mirror of
https://github.com/stablyai/orca.git
synced 2026-10-13 16:02:49 +00:00
faed899cd3088eb740adfa789d141fe0be97fd6e
6626
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e717ea6b4c |
feat(native-chat): copy chat images from the right-click menu (#24161)
* feat(native-chat): copy chat images from the right-click menu
Fixes #23904
* test(native-chat): cover Copy image and the open preview in Electron
* fix(native-chat): keep the image preview open by click target, confirm copies, pass PNGs through
The image preview stayed open through a chat-menu click by reading which
element had focus, which depends on the menu's exit-animation timing.
It now ignores an outside interaction whose target is the chat menu, the
same target check other Orca surfaces use for portaled menus. Both
previews drop their color and padding overrides on the dialog surface,
which the design system reserves to the dialog primitive.
A successful Copy image now shows "Image copied"; before, nothing told
the user the copy had finished.
An image that is already a PNG is copied as-is after the size checks,
instead of being decoded and re-encoded, which cost time and could grow
a screenshot past the clipboard size limit.
* fix(native-chat): pass an image through as PNG only when its bytes are PNG
Copy image skipped re-encoding any image whose type said PNG, but a chat
image's type comes from its file extension. A WebP or GIF saved under a
.png name was then sent unconverted, the main process could not decode
it, and the user saw "Image copied" with nothing on the clipboard. The
pass-through now checks the PNG file signature instead.
* test(native-chat): format combined image-copy and session-ID mocks
* Release chat image menu when the retained pane hides
* Validate inherited style directives in their owning scan
* Copy displayed chat images and refuse hidden menu capture
Use full-size sent-image sources, read HTTP images when copying is chosen,
and rasterize browser-decodable formats through the existing converter.
Preserve the current preview surface and prevent a hidden preview from
retaining another copy action.
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* Revert "Validate inherited style directives in their owning scan"
This reverts commit
|
||
|
|
f6448a1e26 |
fix(native-chat): say which retry a Claude retry is on and what it last failed with (#25818)
* fix(native-chat): say which attempt a Claude retry is on and what it last failed with * fix(native-chat): count Claude's retries as retries, not attempts Claude Code sends its first api_retry frame only after the first request failed, so frame "attempt N of max_retries" is the Nth retry. Say "Retry N of M." and store the bound as maxRetries. |
||
|
|
35e173a5b7 |
fix(native-chat): alert when a structured chat asks for approval or input mid-turn (#25766)
* fix(native-chat): alert when a structured chat asks for approval or input mid-turn A structured Claude or Codex chat that stopped mid-turn to ask for approval or a question raised no OS notification, no phone push and no unread dot: the host's attention feed only fired when a turn settled. The host now announces each newly pending prompt once, from the same feed, and owns the phone push for its own structured sessions; desktops keep presentation only. - host feed: a `prompt` edge per pending approval/question, opt-in on the existing stream; a clean settle that only restates an announced prompt is not re-announced to prompt-aware clients; failures always are - host push: an in-process subscriber pushes the host's paired phones with labels from in-memory metadata, and withdraws a prompt's alert once answered - one attention identity for desktop banner, phone push, dedupe and retirement; delivery dedupes on it instead of the per-workspace burst window - agentSession.acknowledgeAttention routes reading a chat to its owning host, which retires what it pushed for that session via its dismissal record * fix(native-chat): keep prompt alerts under their retirement identity * Fix structured attention read and delivery lifecycle * Allow later reads to retry failed attention retirement * Prevent delayed prompt alerts after accepted reads * Keep attention integration tests across process boundaries * Check structured capability before cursorless attention reads * Keep host retirement fixture in the Node typecheck boundary * Keep attention test seams in the mock boundary * Keep host notification formatting usable without Electron * Declare agents in restored-host attention fixture * Group session attention capability declarations * Retire structured alerts on every read and when a remote prompt ends - Mark read and Mark all read on a chat that was never opened, or is hidden, now cover what lit its row: the read boundary is the later of the transcript cursor and the newest edge the tab was handed, captured at the click. The one boundary closes the banners, retires relayed phone alerts and drives the host acknowledgement; an edge after the click stays live. - An alert from a host older than journal cursors has no position, so a pane read retires it again, banner and relayed phone push, as before this change. - A desktop-relayed prompt alert for a remote chat is withdrawn once that host's live status stops reporting attention (answered elsewhere, cancelled, host restart); cached status after loss of contact never settles it. - The attention acknowledgement is agent-session.attention-ack.v1 with a required journal cursor and strict params; the cursorless no-op is gone. - The dismissal store keeps a record whose origin a newer build wrote, dropping only that origin. - Host reconciliation runs on restore and when a prompt leaves the pending set, not on every journal commit. - Remove unused session-wide retirement (retire, retireMatching, retireMobileNotificationsMatching) and test-only store lookups. * Settle relayed prompts only from host-dated status; reads default to view - A remote chat's status and its prompt edges arrive on separate sockets, so a status row queued before the prompt could land after it and withdraw a relayed alert that was still pending. Settlement now needs a live, non-attention row whose host updatedAt (the journal's latest row time, never decreasing) is later than the prompt's host raisedAt. The owed prompt is cleared only once the settle call succeeds, so a failed call retries on the next qualifying row. - An acknowledgement without captured reads now reads as a view. Mark read, Mark all read, the hover-card jump and both dashboard acks pass 'explicit'; Activity's automatic per-turn read stays a view. * Re-judge relayed prompt settlement after each call; dashboard watching is a view - Settlement is one check of the current mirror, run on each status row, after each settle call returns, and when a prompt edge arrives. A row that lands while a call is out is no longer dropped, and an edge that arrives after its own resolution settles at once. A rejected call is retried only once the mirror holds a different row, so a failing call cannot loop. - The Agent Dashboard's card click acknowledges as explicit; watching the open dialog acknowledges as a view, through both the in-window drawer and the pop-out relay, and no longer re-acks the state the click just read. * Keep a reused read owner findable after its holders release it A chat pane memoizes its read owner. When every holder let go in one commit and the same owner was picked up again (React StrictMode's effect re-run in development, or a pane remounted in one commit, as on a tab move), the release dropped it from the registry and nothing put it back. The attention bridge then found no owner, so a view read carried no cursor: the unread dot cleared but the banner and phone alerts stayed. An owner now names its key again whenever it gains an activation or a subscriber and the key is free, never over a different live owner. * Type the ack test's execution host ids * Keep the turn-completion types in main's turn-completion wire module Main moved these types into agent-session-turn-completion-wire.ts while this branch moved them into agent-session-attention-wire.ts. They now live at main's path with this branch's additions (journal cursor, prompt attention, prompt arm); the unused completion key is dropped. |
||
|
|
53b9ccba6c |
refactor(terminal): add in-place pane layout geometry apply (unused) (#25671)
* refactor(terminal): add in-place pane layout geometry apply (unused) PaneManager.applyLayoutGeometry applies a layout's split orientation and ratios to the mounted pane tree without remounting panes, and never fires onLayoutChanged. Nothing calls it yet; the live-layout reconciler adopts it in a later change. * fix(lint): merge duplicate removal import in delete-worktree failure toast Main (#25668) introduced a duplicate import that fails the focused code-quality lint. * refactor(terminal): share split ratio read; refuse zoomed or dividerless trees in geometry apply |
||
|
|
5345ba34bf |
Fix ownership for terminal and chat file drops (STA-6940, PR 1/6) (#25749)
* Fix terminal and chat file drop destination ownership * fix runtime terminal drop ownership and queued chat retries |
||
|
|
deb737478a |
Simplify reveal and test fixture cleanup (#25978)
* refactor: remove redundant reveal and watcher test wrappers * test: keep closed restore fixtures free of deferred WAL writers |
||
|
|
7bc9ff947c |
fix(grok): add Grok 4.7 to the fallback model catalog (#25976)
grok 1.0.41's `grok models` now reports `Default model: grok-4.7` and its model cache advertises low/medium/high/xhigh effort for grok-4.7. Seed it as the CLI default with an xhigh ceiling, keep grok-4.6 and grok-4.5, and move the probe spec default in step with the seed. Fixes STA-9652 |
||
|
|
d8c871a1f0 |
Speed up unit tests with cross-runtime duration scheduling (#25967)
* Schedule unit tests across runtimes by measured duration * Keep new SQLite fixture suites on Node after updating main * Inject scheduling timings instead of mocking the module * Keep agent-session database lifecycle contracts on Node |
||
|
|
6352ff3ff9 |
Run nested workspace deletion in the background (#25975)
* Run confirmed nested worktree deletion in the background * Reuse the translated workspace deletion failure title * Supply launch arguments in the Codex session test fixture |
||
|
|
c893048aeb |
feat(native-chat): show which agent a chat message is from, and open it (#25888)
* feat(native-chat): show which agent a chat message is from A message another agent sends into a native chat (today the orchestration mail notice) looked exactly like the person's own: a right-hand bubble and a queued card identical to theirs. The sender was recorded only on the queue row, so a message sent to an idle chat, or a card once it was sent, had no sender at all. The sender now lives on the message itself (`from` on the message body): - The send path carries it on both deliveries; an idle chat records it too, and it survives the queue, /clear's carry, restart and history. - No fingerprint covers it: one shared payload projection leaves it out at every fingerprint site, so stored digests are unchanged and the provider's echo still folds into the same bubble. The provider never receives it. - A client cannot set it: its send params stay strict. - One reader normalizes it where it is read (journal load, queued row, client projection); a newer message kind keeps the sender an agent's. - Each sender keeps a name snapshot from names Orca controls: a chat's conversation name or agent label, a terminal's own tab title or agent, a federated sender's task name. Never an agent-set terminal title. - The separate queued-row sender column and its plumbing are removed; it never shipped. Desktop shows such a message as a left-aligned "Message from <name>" row; the name opens the sender: its chat through the existing open-chat flow (after a host lookup that follows /clear and dispatch addresses), or its terminal through the terminal-link path, with the existing feedback when it is gone. A queued card shows a plain "From <name>" line. Mobile shows the same attribution, not yet clickable. * fix(native-chat): name and open an agent's message by what Orca shows for it Review fixes for showing who an agent message is from. - Names: a sender is named by its dispatch's task (local workers too, not only federated ones), else a chat by the label its tab shows, else a terminal by its tab's stored title, else its agent. The conversation name the snapshot read before has no writer, so every chat read "<Agent> Chat". - One sender builder (agentMessageSender) and a public runtime naming object (orchestrationSenderNames) any send site can use; the terminal naming moves out of the unrelated runtime layer into its own module. - Opening a terminal sender always asks the chat's host: a terminal handle is issued per run, so the host finds an earlier run's terminal again through the pane its mail was sent from. The pane key never leaves the host. - Feedback follows what the host found: a gone chat or a gone pane by the kind it lost, "pane no longer available" only when the terminal answer proves it, and the existing "couldn't reach" / "needs a newer Orca" words otherwise. - Another agent's message is not offered rewind (it would put the agent's text in the person's composer), and a Codex rewind keeps the sender on the messages it retains. - The queued card and mobile name every sender, two that share a name included, and count in "+N" only those left out; the transcript row separates names with ", ". - The host no longer normalizes the sender when loading the journal or the queue: nothing on the host reads it, and clients read it through the one reader at their projections. * fix(native-chat): name a worker's final report by its task; clearer open-sender failures - A worker's own worker_done settles its dispatch before the mail lane names who the report is from, so a terminal worker's completion read "Codex". A local worker is now named by the dispatch it holds, else the one it last held (handles are per run, so that is this run's). The coordinator mail rig now attests the worker's process, so its report is accepted and settles as it does in production. - Opening a sender: a version block or an unknown answer says "Update Orca to open it.", since either side may be the older one; "This needs a newer Orca on the computer running this chat" stays only for a host that predates the lookup. "Orca couldn't reach the agent." is followed by "Try again.", each sentence translated on its own. No new copy. * fix(native-chat): name a worker only by work it holds or just reported A worker with no active dispatch was named by the newest dispatch ever assigned to its terminal, at any status and any age: one that failed before it ran, or a task finished long ago that its later, unrelated mail kept showing. Now a worker is named by its active dispatch, else by the dispatch its own worker_done in the announced mail names (only if that dispatch is its own), else by its tab or agent as any terminal is. * fix(native-chat): restore a rewound message's sender through its typed body; mobile card fixtures name their attribution - The rewind's held-sender restore lives with the retained rows and reads both bodies through the journal's message-body check, instead of a shared helper taking any object (anti-slop: no-object-parameters). - Mobile queued-card test fixtures carry the attribution field the card gained. * fix(native-chat): never call a sender gone without proof; the sender builder moves out of the mail lane - A federated sender (dispatch:<id>) runs on another host, which the chat's host cannot open: its name is plain text, and the host's lookup answers 'not found' rather than 'gone' for a dispatch it does not run. Not seeing a worker is no evidence it stopped. - The lookup calls a chat gone only when the identity is proven lost; any other failure reaches the client as 'could not reach'. - A sender open that throws shows 'couldn't reach the agent' instead of an unhandled rejection. - agentMessageSender, the one sender builder, lives in its own module, so the runtime's naming no longer imports from the mail lane. |
||
|
|
6aa12c30ef |
Name native Claude and Codex chats after their first message (#25724)
* feat: generate chat names through configured text agents * Project structured chat names across stored tabs and session rows * Name native chats from their first live message * Restore the journal test provider handle import * Fix first-message naming and live Vault title updates * Preserve Unicode characters in bounded chat naming prompts * Read chat naming settings only when a turn needs them * fix(chat): store only generated conversation names * fix(chat): keep ordinary labels across unnamed chat surfaces * fix(chat): preserve naming after fast first turns * Preserve native chat names across command-first sends and Vault lifecycle * Route native chat SQLite contracts through the existing Node test pool * feat(settings): add chat naming controls to Chat page * Preserve chat naming drafts and configure Custom commands through host settings * Scope synthetic command output to its journal thread in naming test * Keep naming test fixtures within their typed project boundaries * Resolve the real Vault hook directly in its integration test * Publish chat naming save refs after render commits * fix: keep Japanese chat naming copy stable during repair * Update test contracts for the naming integration * Keep journal fixture reads on the host clock * Give Chat names a separate settings section |
||
|
|
e813fa5819 |
feat(native-chat): keep a message a Stop took back on screen, with one stop row after it (#25051)
* refactor(native-chat): derive the queue's pause from Stop and Resume journal rows
Stop now appends one journal row where it takes effect, before the interrupt,
whatever the queue holds; Resume appends its own. The pause is a pure function
of the fold: the latest Stop with no later Resume and no later accepted turn a
person asked for. A /clear's carried cards name their source, which is the
replacement's 'cleared' pause. Host-origin turns never lift either.
One predicate decides which cards a pause holds; by default every waiting card
without a hold of its own, including one queued after the Stop. The drain's
consume re-judges it inside its own transaction.
The rows are tombstones of an id no item takes, carrying the mark: a released
host reads an unknown row kind as corruption and truncates the journal there.
Deletes the stored pause (recordPause, the retire hook on every appended row,
the settle-before-record step, mayReturnToWaiting and its row overlay) and the
tests that only proved it retires. The queued_message_pauses table stays in the
schema, unread and unwritten, for downgrade safety.
* fix(native-chat): a card queued after a Stop sends normally, never ahead of held ones
A Stop's pause now holds only the cards queued before its row, plus a steer it
withdrew, which returns to its own place. Each card records the journal
position it was queued at, and the one hold rule compares that with the Stop
row. A card queued after the Stop is a new instruction: it sends as usual, but
the drain still stops at the first held card, so it never overtakes them.
/clear's pause holds the cards it carried. Holding every card again is a
one-line switch in that rule.
* fix(native-chat): the queue's own send re-checks the no-overtake rule in its transaction
The drain's pick and its consume now read one function, nextSendableQueuedCard,
so a Stop row that lands between them holds a newer card behind an older held
one exactly as the pick would. Notes why Stop and Resume ride a tombstone row.
* fix(native-chat): stop creating the unused queue pause table
The queue's pause is derived from journal rows, so nothing reads or writes
queued_message_pauses. It was still created on every open "for downgrade
safety", but an older build creates it itself when it opens the database, so
the table only sat empty in every new database. The tests now pin that no
pause table exists.
* fix(native-chat): a Stop's pause never hides the restart pause
A Stop holds only the cards queued before it. The pause derivation still
returned the Stop alone whenever it was in force, so the restart pause was
never considered: a card queued after the Stop, written by a host process
that has since exited, sent by itself after Orca restarted, with no pause
header and no Resume. A /clear pause that held nothing could hide it the
same way.
Every pause in force is now derived. A card is held if any of them holds
it, and it names the first that does. The drain's pick, the consume
transaction's re-check and the published header all read that one rule;
the header names the pause holding the first card Resume would send.
* test(native-chat): pin the Stop's no-resend, lift and held-card rules
- The Claude and Codex Stop-withdraws-a-steer tests checked "not sent
again" at one instant, before a queue ignoring the pause re-sends. They
now wait for the stopped turn to end and re-check after a quiet window.
- The deleted-card test read a card queued after the Stop, which sends
whether or not a person's turn lifts it; it now reads the Stop's pause
before and after that turn.
- Unit cases pin that a Stop holds a card with no recorded position and one
queued before a rewind.
* refactor(native-chat): a Stop writes one Stop event with its reason, turn and caller
The Stop row that paused the queue becomes the general Stop event
{ reason, turnId?, at, caller? }, whose reason is the host's existing stop
cause. It still rides a tombstone of a host-only id (a released host deletes
the journal from the first unknown row kind), and Resume keeps its own marker
on its own id. Only a person's Stop (reason user-stop) pauses the queue.
* test(native-chat): a rewind keeps a lifted /clear pause lifted and restates the same Stop event
* test(native-chat): pin that Stop and Resume rows never reach apps or count as history
* test(native-chat): only a person's Stop event pauses the queue
* test(native-chat): pin that a Stop's event precedes the interrupt and the at-start stop
Through the real host: the event names the turn and who asked and is in the
journal when the interrupt reaches the agent; at an agent still starting it is
there before the start is ended and holds a card queued before it; an idle Stop
writes one only when it withdrew a send; and the queue's claim re-judges a
pause that landed after its pick.
* test(native-chat): a card held at a starting agent is checked before the Stop's timing
Also says precisely what the claim's in-transaction pause check defends
against: the Stop and the drain share one serialized lane.
* test(native-chat): a released build keeps and folds a journal holding Stop events
Replays this build's rows from the released build's own journal database: every
row is kept, the history after the Stop still folds, and an older client is sent
only removed ids no item uses.
* style(native-chat): format the Stop event changes
* test(native-chat): type the released build's exports through one checked helper
* fix(native-chat): the Stop/Resume row guard narrows to those tombstones only
* test(native-chat): run the Stop-event downgrade test in CI, and cover a writable downgrade
The Stop-event downgrade test ran in no CI lane: unit shards exclude the
cross-version folder, and the cross-version lane runs a fixed file list that
did not name it. It is now on that list.
Its only case replayed the rows into a release's own fresh database, because
that release cannot open the current host database. A second case opens the
journal this build wrote with a main build that shares the database: it opens
writable, keeps every row, appends, and this build then reopens it with the
person's Stop still pausing the queue.
* fix(native-chat): a Stop that stops nothing new writes no Stop event
A Stop reaching a running agent wrote a Stop event on every press. Two
presses before the first interrupt landed wrote two events, so a card
queued between them counted as before the latest Stop and was held,
though a card queued after a Stop should send normally. A Stop naming a
turn that had already ended, as a phone sends late, also wrote an event
for a turn it never stopped.
It now writes one only when it withdrew a queued send, or stops something
no event records yet: not a turn the journal no longer runs, and not the
live turn a Stop still in force already names, unless a card was handed
over into it since, which this Stop's interrupt sends back and must hold.
The interrupt and the "already finished" note are unchanged. A Stop at a
starting agent still always writes.
* test(native-chat): pin that a later host, eviction or close Stop never lifts a person's Stop
* chore(native-chat): put each Stop-row doc on its own declaration, and say only user-stop is journaled
* fix(native-chat): any later Stop event ends a person's Stop pause
A person's Stop paused the queue until their next accepted turn or Resume,
and a later Stop of another reason (the host stopping the agent, an
eviction, a close) was ignored. Now the pause is the latest Stop event's:
a later Stop of any reason ends a person's pause, and only a person's Stop
pauses. The fold keeps the latest Stop event whatever its reason.
An eviction of a resting chat writes no Stop event (a Stop that stops
nothing writes nothing), so it cannot release held cards; a test pins that
no event means no lift.
* fix(native-chat): a second Stop press is a repeat even when the first came before the turn showed
A Stop pressed before the agent's turn shows in the journal (before
Claude's echo, or before Codex opens the turn) records no turn. A second
press once the turn showed compared that missing turn with the live one,
wrote a second Stop event, and held a card queued between the presses.
A repeat is now judged by what was sent since the Stop in force: with
nothing sent after it (a refused send aside), a Stop that named no turn,
or named the live one, is repeated and writes nothing. Anything sent since
and not refused, including a send whose fate is unknown, makes the new
press write, since its interrupt may send that card back to waiting.
Tests: the two-press case across the turn showing; a steer between the
presses settled unknown; and a Stop naming a turn that ended while the next
card is sent but shows no turn yet, which writes and holds that card. The
fold test that claimed an eviction path is renamed.
* fix(native-chat): the queue's pause ignores a Stop or Resume row holding a value no build writes
A Stop or Resume row's value is read from disk with no shape check, and
the pause fold stored whatever it found. A stored `stopEvent: null` would
then throw on every pause check for that chat: the queue's pick, its
send, and every queue update to clients. No build writes such a row, so
this is hardening.
The fold now reads a Stop only when it is an object with a string reason
and a finite time, and a Resume only when it is `true`. Anything else is
ignored: it pauses nothing and ends nothing. The row is still not treated
as malformed, which could cut the history short.
* fix(native-chat): a Stop still reads as yours after Orca restarts before the turn ends
Every stop that ends work now writes the Stop's event before it ends the child: a
person's close of the chat, an eviction (worktree teardown, orchestration stop, tab
cleanup) and the idle sweep's stop of a start that never landed. A stop that ends
nothing writes nothing, and quit writes none: its resume marker records why.
The turn-end write reads the latest Stop event where every turn row is built, so the
adapter's settle, the host's fallback and the relaunch's settle all agree: a turn a
person's Stop or close named, ending with no verdict of its own after that Stop, ends
as their cancellation. A relaunch's probe-bounded end is no earlier than a Stop that
found the turn running. When the provider refuses the interrupt and the turn runs on,
a refusal row answers the Stop, so a later crash still reads Failed; pressing Stop
again after a refusal is a new Stop.
* refactor(native-chat): a stop no longer carries its cause; the turn's end reads the Stop event
The cause of a stop was threaded in memory from each entry through the host's stop
step, the adapter router and each adapter's close onto the `ended` it settled with,
and Claude kept a per-turn copy of a Stop it sent. All of that is gone: adapters
settle a turn they cut as interrupted with no verdict, the host's fallback does the
same, and the one rule where a turn row is built (`turnEndAfterStop`) reads the
journal's latest Stop event to say whether it was a person's.
- `closeSession` / `disposeSession` take no cause; `ended` has no `stopCause`.
- Claude reads an error result after a person's Stop as their cancellation from the
journal's Stop event (through the event sink), not from a per-turn slot, and a
refused interrupt is the host's refusal row, not `withdrawTurnStop`.
- An owed wind-down keeps no cause: its retry's fallback reads the Stop event.
- The mutation context's Stop passes no cause: its step already wrote the event, and
the delivery loop's child-end reason is read back from it.
- A Stop pressed before its turn showed applies to the turn that opens under it,
unless a send a person made since was accepted.
* test(native-chat): a turn a later send opened is no Stop's that named no turn
* test(native-chat): the restart test's death proof carries its detail
* refactor(native-chat): a refused Stop leaves no record; a Stop only ever ends the turn it names
The stop-refused mark is gone: its tombstone kind, its fold, the clock-keyed match that tied it to
a Stop, and the exception that let a second press after a refusal write a new Stop. A Stop that
stops nothing writes nothing. A Codex refusal names a turn that is no longer its active one, and
the Stop names that turn, so the turn running instead never reads as the person's by its id alone.
* fix(native-chat): a Stop pressed before any turn showed stops only the turn opened next
A Stop that named no turn read as the person's cancellation for every later turn that opened
after it, until a send a person made was accepted. The queue's drain, orchestration mail and a
restart continuation send as the host, so a turn they opened long after, cut by a crash, read
"Interrupted" as if the person had stopped it. The Stop now applies only to the first turn
opened after it.
* fix(native-chat): an older Claude's error end after a Stop pressed before its echo reads Interrupted
Claude CLIs before 2.1.91 end an interrupted turn with an error result that names no reason. The
translator judged whether a person's Stop explained it by its own copy of the Stop rule, which
ignored a Stop that named no turn, so a Stop pressed before Claude echoed the send read "Failed".
The translator now writes such an end as interrupted with no verdict and no error row whenever a
person's Stop may name the turn, and the journal's one rule decides as it writes the end.
* fix(native-chat): a person's Stop and /clear each name why they end the agent
The host's mutation path ended the agent with one "recorded" ending for every caller, which read
back the reason of whatever Stop event the journal held last, however old. /clear writes no Stop
event, so its end took an unrelated earlier reason. Each caller now names its own: the chat's Stop
`user-stop`, whose event its own step wrote, and /clear `user-close`, the user replacing this chat.
* fix(native-chat): a host stop judges whether it ends work after the provider's rows land
A close, eviction or host stop decided whether it ended a running turn from the journal as it
stood, while the provider's own rows (the turn its echo opened) could still be in the session's
event sink. A close landing in that gap wrote no Stop event, so the turn it cut read as news. It
now reads after the sink drains, as a person's Stop does, through the same check; a drain that
fails or takes over a second reads working.
* fix(native-chat): a Claude Stop naming a turn that just ended still marks the follow-up it cuts
A phone names the turn it last saw. When that turn had ended and a follow-up was still unechoed,
Claude's Stop interrupted the follow-up and ended the child, but the Stop's event named the ended
turn, so the follow-up's turn the child's end cut read "Failed" under "Cancellation requested.".
A Stop that ends the provider's session ends whatever is in flight, so its event now names the
live turn or none, and a Stop that names none binds the turn opened next. Codex keeps naming only
the turn the Stop names.
The Claude Stop turn-end tests move to their own file, since the session-ending Stop suite is at
its line budget.
* fix(native-chat): the idle sweep reads working by the same rule as a stop's event
The sweep judged a chat resting while a send whose reply was lost was still unanswered, but the
stop's event writer counts that send as work. So the sweep evicted it and wrote an evict event,
which ends a person's Stop pause and let the cards behind it drain on their own. The sweep's owed
work now reads the main agent working the way every session list and the event writer do.
* test(native-chat): an aborted eviction's injected drain failure lands on the eviction's own drain
A host stop now drains the session's sink once to judge whether it ends work, so the tests that
fail the eviction's drain-published step skip that first drain.
* fix(native-chat): the idle sweep's rest writes no Stop event; it evicts a send that never echoes
The previous commit made the sweep count an unanswered send as owed work, which pins a chat whose
admitted send Codex never echoes forever, and the sweep exists to retire exactly that. That rule
returns. The sweep stops only an agent it judged resting, so its eviction now writes no Stop
event, whatever send it retires: a person's Stop pause holds through it.
* fix(native-chat): stopping a start that carries no send writes no Stop event
A host stop, eviction or close of a starting child wrote a Stop event whatever the start carried.
A start with a send already reads working, so the clause only mattered for a start with none,
which ends no turn and no send: its event only lifted a person's Stop pause and bumped the idle
clock, which is why the idle sweep had been changed to close the conversation in the same pass.
The clause goes and the sweep is #24072's again. The child's end still reads host-stop, as before.
* test(native-chat): a Stop's pause across a restart is tested with a restart that writes no event
The rig's restart closes the chat with an eviction, which now writes a Stop event when work runs
and so ends a person's Stop pause. "A Stop never hides a restart's pause" then passed with no Stop
pause left to hide anything. Those tests, and the pause-lift test whose dropped assertion returns,
restart as a process that dies with no close, which like a quit writes no Stop event, and assert
that both the Stop's and the restart's pauses are in force first.
* fix(native-chat): a host stop of a turn a person's Stop is still ending keeps that Stop's reason
An eviction or host stop that landed while a person's Stop or close was already ending the same
turn wrote a newer Stop event, and the turn's end reads only the latest, so the person's Stop of
that turn read as news. A host reason now writes nothing while a person's Stop still decides what
runs: the live turn it names or bound, or, with none, the turn a send opens next. The person's
own close still writes. The E2 tests now open and end the stopped send's own turn, as Codex does,
so the mail turn after it is not the turnless Stop's.
* fix(native-chat): an older Claude's error on a later turn keeps its error text after a Stop
The translator left an error result that names no reason to the journal's Stop rule whenever a
person's Stop named the turn or none, but the rule binds a Stop naming no turn only to the turn
opened next. So a real error on a later turn read "Failed" with its error text dropped. The
translator now asks the journal's rule itself (`personStopDecidesTurn`, the one core
`turnEndAfterStop` and a host stop's in-force check share), so the two cannot disagree.
* fix(native-chat): a Stop of a start that never landed binds no later turn, whatever sent it
A person's Stop pressed while the agent starts names no turn, and the send it stopped is
cancelled before it opens one. The Stop then bound the next turn anything opened (orchestration
mail, a restart continuation, the queue's drain, all of which send as the host), so a host
eviction of that turn wrote nothing and its crash or close read as the person's cancellation. A
Stop that named no turn now binds only a turn no send journaled after it opened: any send since,
of any origin and not refused, opens its own. The E2 test's mail send is accepted as Codex
accepts it, instead of opening the stopped send's own turn first.
* test(native-chat): a rewind's restated turnless Stop binds no turn opened after the rewind
A Codex rewind restates a person's Stop still in force after the turns it keeps, at a new
sequence, so by sequence alone it would bind the next turn opened after the rewind. A send
journaled after the restated row voids that binding (the previous commit), which this pins.
* fix(native-chat): a relaunch settles a person's stopped turn with no "stopped while in progress" row
After a restart, a turn a person's Stop ended reads "Interrupted after N" with the muted mark, but
the relaunch still added the error row saying the provider stopped mid-response, which a live Stop
never writes. The settle now skips that row when every turn it interrupts is the person's Stop's
by the journal's one rule; a crash nobody stopped keeps it.
* test(native-chat): the unexpected-exit settle's journal fake answers whether a person's Stop decides a turn
* fix(native-chat): a host stop whose sink drain fails reads the journal as it stands
A host stop drains the session's sink before judging whether it ends work, and a failed or slow
drain read as working. So an eviction of an agent at rest wrote a Stop event that ended nothing,
which lifts a person's Stop pause, and a close wrote a person's event naming no turn. The drain is
now best effort: the stop goes ahead either way and only its record is at stake, so a failed or
slow drain leaves the journal's read as it stands. A person's Stop keeps its own rule.
* fix(native-chat): a Stop that named no turn applies only to a turn a send it stopped opened
A person's Stop pressed before any turn showed names no turn. It bound the first turn opened
after it, then (
|
||
|
|
28ed544da8 |
Fix send picker reveal under collapsed hosts and parents (#25950)
* fix: reveal send picker targets through host-owned ancestors * test: type picker close fixture as a nullable target |
||
|
|
0a26f3ec90 |
fix(native-chat): honor saved agent Command and Arguments (#25721)
* fix(native-chat): honor saved agent launch commands and arguments * fix(test): retain provider import after syncing main * fix(native-chat): preserve launch arguments and explain invalid settings * refactor(native-chat): require the current launch arguments source * fix(native-chat): require saved arguments when installing the runtime * fix(native-chat): show saved argument refusals beside launch retry * fix: ship saved-argument error messages in every locale * test: align model discovery with executable overrides * chore: drop the local pnpm install lockfile block |
||
|
|
ce4c99ae07 |
fix(terminal): keep a pane closed just before quit from coming back on relaunch (#25711)
* fix(terminal): keep a pane closed just before quit from coming back on relaunch The daemon now lists a session whose kill it accepted as state 'exiting' (still live), the inventory carries that through to pty:listSessions, and the worktree activation gate never adopts an exiting PTY into a new tab. Co-Authored-By: Claude <noreply@anthropic.com> * test(e2e): use the Linux close-pane chord (Ctrl+W) in the closed-pane relaunch spec --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
7e52bed4e0 |
fix(editor): recover PDF previews after failed refreshes
Recover PDF previews after failed refreshes, clear previous pages on changed contents, and preserve scroll and zoom while releasing superseded resources. Co-authored-by: sotashimozono <shimozono-sota631@g.ecc.u-tokyo.ac.jp> |
||
|
|
53edf8ec4b |
fix: recover deleted Active Server defaults and preserve editor hosts (#25938)
Repair deleted Active Server defaults through validated registry reads and existing settings notifications. Capture editor ownership from backing files so workspace focus cannot redirect file operations. Correct two live-turn test fixture calls to the current API. Refs #21489. Historical session recovery remains separate work. Co-authored-by: Chen <zwq19980411@gmail.com> |
||
|
|
83cf7cf5e2 |
perf(ci): compile release JavaScript once for all packaging hosts (#25828)
* perf(ci): share release JavaScript across packaging hosts * fix(ci): verify the projected web entry in release archives * fix(ci): use the Windows system archive tool for release bundles * test(ci): retain stylesheet evidence in release build comparisons * test(ci): verify release parity across native color rounding * test(ci): normalize manifest asset references without changing import order * test(ci): compare portable outputs across Windows text and color formatting * test(ci): preserve module identity across dependent asset hashes * fix(ci): keep SVG build inputs identical across release hosts * fix(ci): stabilize compiler inputs and projected web bindings * fix(ci): retain vendor minification in projected web output * test(ci): normalize platform-specific pnpm manifest source paths * fix(packaging): exclude shared build staging from application files * test: align thinking-state fixtures with the current source shape * test(mobile): reuse message fixtures within the line limit |
||
|
|
6a71732879 |
Keep sidebar section collapse independent across execution hosts (#25944)
* fix(sidebar): scope lane collapse and reveal by execution host * fix: consolidate sidebar reveal group key imports * test: publish cancellation readiness after signal setup |
||
|
|
2f001fe880 |
fix(remote-browser): host client browser pages again when a paired host comes back after an outage (#25827)
* test(remote-browser): reproduce a client-hosted browser tab left unavailable after a network outage A paired desktop that loses its host for longer than the browser-host lease grace (25s reset cut, or a 100s silent cut the host's heartbeat reaps) tears down its client-hosted pages and never hosts them again, leaving 'Client-hosted browser unavailable'. Red on main. Extends the freezable TCP proxy with a full silent or reset link cut. * fix(remote-browser): host client browser pages again when a paired host comes back after an outage When the network between a paired desktop and its Orca host dropped for longer than the browser-host lease grace, the desktop tore down its client-hosted pages and never offered to host them again, so the tab stayed on 'Client-hosted browser unavailable' until the app was relaunched. Regaining contact with a runtime now re-claims browser hosting the same way a runtime restart already did: if this desktop still has client-placed pages for that host, it starts its browser client host again and the host's existing recovery re-places them, reloading at their last URL. The notice now says the host is offline (and that the page reloads when it reconnects), names another desktop only when the placement belongs to one, and otherwise says the page is not available here right now. * fix(remote-browser): claim offline only for a host that can reconnect, and re-host from the notice The unavailable notice said the host was offline for any null status, which also covers a retired or refused pairing and a host whose runtime did not answer. It now uses the host connection state and excludes revoked verdicts. When the control link returns while the old browser host is still inside its lease reconnect, the contact-edge ensure is a no-op; if that reconnect then fails, nothing re-hosts. The notice now re-claims hosting on mount while the host is reachable, which is exactly that stranded state. Plain regained contact no longer queues a second preparation behind one already in flight; only a runtime restart does. * fix(remote-browser): re-prepare browser hosting on every regained contact A preparation still in flight when contact returns started while the host was unreachable, so it is the one most likely to fail. Coalescing the contact edge into it dropped the only trigger that could succeed and left the page unavailable. Always queue one follow-up preparation instead. |
||
|
|
a0a7535b63 |
fix(native-chat): say each chat outcome once and drop internal-only surfaces (#24595)
* fix(native-chat): say each chat outcome once and drop internal-only surfaces After a restart, resuming a chat reported the same outcome in up to four places: a toast, the chat's own note, the status bar entry and the resume dialog. Success also toasted, and a lost answer or unconfirmed dismissal raised toasts of their own. The chat's note and the status bar entry (which opens the dialog listing every affected chat) now carry it alone, and the resume guidance no longer talks about which process "owns this session". Provider events Orca does not recognise and that carry no words of their own are logged instead of journaled as raw "codex · notification:foo" rows. Rows with the provider's own sentence, failures, and records of replies Orca already sent stay. The approval card no longer shows the matched ask rule, its settings source or a "Blocked path" line; it keeps the request, its detail and the reason. * fix(native-chat): store wordless provider events, hide them only when drawn Dropping text-less provider events on the host also dropped Codex's turn/plan/updated row, which the task list (checklist and composer progress panel) is built from. The host now journals these rows exactly as before, and desktop and mobile chats leave out a row whose only text is the host's "provider · kind" label. Failures, notices, rows with the provider's own sentence and records of requests Orca answered still show. A hidden legacy Codex collab-call row still counts as the agent acting for which subagent section stays open. Claude's local slash command output is read from `content` for that one event kind only, so it shows as the row's text instead of disappearing. * fix(native-chat): rows no client draws never fill the Codex cap or split a turn's work - Codex per-turn cap: a wordless provider row (decided by the same shared rule the clients use) has its own silent storage bound, so it no longer crowds out rows that draw and never produces a "N more provider notifications not shown" line about rows nobody sees. Plan updates are exempt from the cap, so the task list keeps updating after a noisy turn. - A Claude local command's row an older host stored with only its label (/usage output in the payload) stays drawn instead of disappearing. - A settled turn whose only extra row draws nothing offers no expand arrow, and a wordless row no longer splits one tool run into two. * fix(native-chat): a resume request that never reached the chats says so once, with Retry When the resume request fails before the host reserved anything (the host is not ready, or its recovery record cannot be written), the host has nothing to record, and the chats used to fall silently back to "N chats to resume". The status bar and dialog now show them as failed, with the existing guidance and Retry, until Retry, Dismiss, a host answer that no longer offers them, or a relaunch. Comments that still cited the removed toast are corrected. * fix(native-chat): keep mobile tool runs whole and a failed Dismiss's chat marked - Mobile draws no task list, so it now drops wordless provider rows, plan updates included, before folding tool runs; a mid-run plan update no longer leaves two run headers back to back. - A Dismiss the host never took keeps a lost resume request's chat shown as failed instead of turning it back into a plain offer. The lost request's real error now goes to the renderer log, since the row carries this side's own reason code. - Comments corrected: the Codex cap no longer claims dropped wordless rows stay countable, and the resume comments cover a chat a lost request never reached. * fix(native-chat): tell the user what their Resume click did, in one toast Clicking Resume in the dialog (or Retry on a row) closes the dialog and acts on chats that are mostly off-screen, and nothing said what happened. One toast now summarises that click: "Resumed N chats and asked them to continue", "N chats couldn't be resumed" with Show (opens the dialog), "Couldn't confirm N chats were resumed", or the mixed case in the same toast. A request lost before it reached the chats counts its chats as not resumed. An opted-in automatic resume at launch raises no toast; dismissal and other bookkeeping stay silent. * fix(native-chat): Show on the Resume toast re-reads before opening; fix toast translations Show could open nothing when the host retired the failure while the toast was up, because the dialog draws nothing without rows and the request stayed latched. Show now goes through the status bar entry's re-read-then-open path, moved into the store so both use it. The French, Japanese, Korean and Chinese "Resumed N chats and asked them to continue" strings are rewritten to match their sibling strings. Tests pin Show on the toast of a lost resume request whose chats the re-read still lists. * test(native-chat): click the Resume toast's Show through a typed helper The tests reached sonner's action handler with Reflect.apply, which the anti-slop lint rejects. One test-support helper now returns the last toast's Show as a typed click. * fix(native-chat): name the path an approval needs when the request doesn't show it The approval card dropped Claude's blocked path along with the ask rule, so a request like `git push` that needs access to `.git/config` no longer said what it touches. Bring the path back as "Needs access to: <path>", only when the description, reason or request detail doesn't already show it (verbatim or JSON-escaped). One shared rule serves desktop and mobile; the ask rule stays out. * fix(native-chat): count a blocked path as shown only when the text names the whole path '/srv/data' inside '/srv/data-old/x' hid the "Needs access to" line although the path itself was never shown. A match now needs the next character to end the path: end of text, whitespace, a quote or a separator. Also pin that the desktop approval mapping passes the blocked path through and drops the ask rule. * fix(native-chat): the automatic resume at launch gets the same one outcome toast as a click An opted-in launch resume now calls the same announce as a clicked Resume, with the same copy: how many resumed, how many couldn't (with Show), unconfirmed, or mixed, said once. Dismiss and bookkeeping stay silent; a launch whose chats all moved on raises nothing. * fix(native-chat): the resume success toast says "Resumed N chats" Each chat's own note already says Orca asked it to continue, so the toast drops "and asked it/them to continue" in every locale; the couldn't-resume and couldn't-confirm counts and Show are unchanged. |
||
|
|
81a1968bc6 |
feat(native-chat): attach several files from one picker selection (#23956)
* feat(native-chat): attach several files from one picker selection Picks over 256 files are rejected whole, matching the drop limit. Fixes #23905 * refactor(native-chat): name the composer action for the files it now attaches The hook still returned `pickAttachment` after it started attaching a whole selection. Also records why the plural IPC handler sits next to the singular one, matching its siblings in the same file. * test(native-chat): cover the paperclip forwarding every picked path The one line that makes the feature work — the composer handing the whole picker result to attachExternalPaths — had no test; reverting it to paths[0] kept every existing test green. Adds a hook test for the multi-path, canceled, and wrong-pane cases, plus the missing canceled case for the shell:pickAttachments handler. * test(native-chat): exercise multi-pick through renderHook * test(settings): preserve label fallback and verify Spanish copy Keep latest main's missing-label fixture and restore its translation namespace after cleanup, so later tests still load real catalogs. Independently assert the shipped Spanish resource and rendered supported-agents label. --------- Co-authored-by: Neil <neil@stably.ai> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
b922342199 |
feat(native-chat): the chat reads "Stopping…" from your Stop until the turn actually ends (#24369)
* test(native-chat): a Stop over a card sent now into the running turn keeps it paused
Red on main: Codex's turn end withdraws the steered hand-off and the queue
sends the card again as a new host turn, with no pause recorded.
* fix(native-chat): a Stop's queue pause holds a card whose hand-off is still unanswered
A card sent now into the running turn was still pending when Stop judged the
pause, so nothing was recorded; the interrupt then withdrew the hand-off, the
card went back to waiting unpaused, and the queue sent it again as a new turn.
The pause now counts a hand-off that may still return to waiting, judged with
the appended row applied, so a withdrawal lands under the pause and an
acceptance retires it in that same write. Codex and Claude both hit it.
* test(native-chat): the Claude re-send case fails on its diff, inside the test's budget
* test(native-chat): a pause held by an unanswered hand-off ends on every path that ends it
The provider's answer, the provider dying, the chat closing, a restart, a
withdrawal still owed at open, and a /clear (refused until the hand-off ends,
then carrying every waiting card paused 'cleared'); each ends with the queue
sending again.
* fix(native-chat): narrow the pause's settled hand-off, and assert the queued receipt's card
* refactor(native-chat): derive the queue's pause from Stop and Resume journal rows
Stop now appends one journal row where it takes effect, before the interrupt,
whatever the queue holds; Resume appends its own. The pause is a pure function
of the fold: the latest Stop with no later Resume and no later accepted turn a
person asked for. A /clear's carried cards name their source, which is the
replacement's 'cleared' pause. Host-origin turns never lift either.
One predicate decides which cards a pause holds; by default every waiting card
without a hold of its own, including one queued after the Stop. The drain's
consume re-judges it inside its own transaction.
The rows are tombstones of an id no item takes, carrying the mark: a released
host reads an unknown row kind as corruption and truncates the journal there.
Deletes the stored pause (recordPause, the retire hook on every appended row,
the settle-before-record step, mayReturnToWaiting and its row overlay) and the
tests that only proved it retires. The queued_message_pauses table stays in the
schema, unread and unwritten, for downgrade safety.
* fix(native-chat): a card queued after a Stop sends normally, never ahead of held ones
A Stop's pause now holds only the cards queued before its row, plus a steer it
withdrew, which returns to its own place. Each card records the journal
position it was queued at, and the one hold rule compares that with the Stop
row. A card queued after the Stop is a new instruction: it sends as usual, but
the drain still stops at the first held card, so it never overtakes them.
/clear's pause holds the cards it carried. Holding every card again is a
one-line switch in that rule.
* fix(native-chat): the queue's own send re-checks the no-overtake rule in its transaction
The drain's pick and its consume now read one function, nextSendableQueuedCard,
so a Stop row that lands between them holds a newer card behind an older held
one exactly as the pick would. Notes why Stop and Resume ride a tombstone row.
* fix(native-chat): stop creating the unused queue pause table
The queue's pause is derived from journal rows, so nothing reads or writes
queued_message_pauses. It was still created on every open "for downgrade
safety", but an older build creates it itself when it opens the database, so
the table only sat empty in every new database. The tests now pin that no
pause table exists.
* fix(native-chat): a Stop's pause never hides the restart pause
A Stop holds only the cards queued before it. The pause derivation still
returned the Stop alone whenever it was in force, so the restart pause was
never considered: a card queued after the Stop, written by a host process
that has since exited, sent by itself after Orca restarted, with no pause
header and no Resume. A /clear pause that held nothing could hide it the
same way.
Every pause in force is now derived. A card is held if any of them holds
it, and it names the first that does. The drain's pick, the consume
transaction's re-check and the published header all read that one rule;
the header names the pause holding the first card Resume would send.
* test(native-chat): pin the Stop's no-resend, lift and held-card rules
- The Claude and Codex Stop-withdraws-a-steer tests checked "not sent
again" at one instant, before a queue ignoring the pause re-sends. They
now wait for the stopped turn to end and re-check after a quiet window.
- The deleted-card test read a card queued after the Stop, which sends
whether or not a person's turn lifts it; it now reads the Stop's pause
before and after that turn.
- Unit cases pin that a Stop holds a card with no recorded position and one
queued before a rewind.
* refactor(native-chat): a Stop writes one Stop event with its reason, turn and caller
The Stop row that paused the queue becomes the general Stop event
{ reason, turnId?, at, caller? }, whose reason is the host's existing stop
cause. It still rides a tombstone of a host-only id (a released host deletes
the journal from the first unknown row kind), and Resume keeps its own marker
on its own id. Only a person's Stop (reason user-stop) pauses the queue.
* test(native-chat): a rewind keeps a lifted /clear pause lifted and restates the same Stop event
* test(native-chat): pin that Stop and Resume rows never reach apps or count as history
* test(native-chat): only a person's Stop event pauses the queue
* test(native-chat): pin that a Stop's event precedes the interrupt and the at-start stop
Through the real host: the event names the turn and who asked and is in the
journal when the interrupt reaches the agent; at an agent still starting it is
there before the start is ended and holds a card queued before it; an idle Stop
writes one only when it withdrew a send; and the queue's claim re-judges a
pause that landed after its pick.
* test(native-chat): a card held at a starting agent is checked before the Stop's timing
Also says precisely what the claim's in-transaction pause check defends
against: the Stop and the drain share one serialized lane.
* test(native-chat): a released build keeps and folds a journal holding Stop events
Replays this build's rows from the released build's own journal database: every
row is kept, the history after the Stop still folds, and an older client is sent
only removed ids no item uses.
* style(native-chat): format the Stop event changes
* test(native-chat): type the released build's exports through one checked helper
* fix(native-chat): the Stop/Resume row guard narrows to those tombstones only
* test(native-chat): run the Stop-event downgrade test in CI, and cover a writable downgrade
The Stop-event downgrade test ran in no CI lane: unit shards exclude the
cross-version folder, and the cross-version lane runs a fixed file list that
did not name it. It is now on that list.
Its only case replayed the rows into a release's own fresh database, because
that release cannot open the current host database. A second case opens the
journal this build wrote with a main build that shares the database: it opens
writable, keeps every row, appends, and this build then reopens it with the
person's Stop still pausing the queue.
* fix(native-chat): a Stop that stops nothing new writes no Stop event
A Stop reaching a running agent wrote a Stop event on every press. Two
presses before the first interrupt landed wrote two events, so a card
queued between them counted as before the latest Stop and was held,
though a card queued after a Stop should send normally. A Stop naming a
turn that had already ended, as a phone sends late, also wrote an event
for a turn it never stopped.
It now writes one only when it withdrew a queued send, or stops something
no event records yet: not a turn the journal no longer runs, and not the
live turn a Stop still in force already names, unless a card was handed
over into it since, which this Stop's interrupt sends back and must hold.
The interrupt and the "already finished" note are unchanged. A Stop at a
starting agent still always writes.
* test(native-chat): pin that a later host, eviction or close Stop never lifts a person's Stop
* chore(native-chat): put each Stop-row doc on its own declaration, and say only user-stop is journaled
* fix(native-chat): any later Stop event ends a person's Stop pause
A person's Stop paused the queue until their next accepted turn or Resume,
and a later Stop of another reason (the host stopping the agent, an
eviction, a close) was ignored. Now the pause is the latest Stop event's:
a later Stop of any reason ends a person's pause, and only a person's Stop
pauses. The fold keeps the latest Stop event whatever its reason.
An eviction of a resting chat writes no Stop event (a Stop that stops
nothing writes nothing), so it cannot release held cards; a test pins that
no event means no lift.
* fix(native-chat): a second Stop press is a repeat even when the first came before the turn showed
A Stop pressed before the agent's turn shows in the journal (before
Claude's echo, or before Codex opens the turn) records no turn. A second
press once the turn showed compared that missing turn with the live one,
wrote a second Stop event, and held a card queued between the presses.
A repeat is now judged by what was sent since the Stop in force: with
nothing sent after it (a refused send aside), a Stop that named no turn,
or named the live one, is repeated and writes nothing. Anything sent since
and not refused, including a send whose fate is unknown, makes the new
press write, since its interrupt may send that card back to waiting.
Tests: the two-press case across the turn showing; a steer between the
presses settled unknown; and a Stop naming a turn that ended while the next
card is sent but shows no turn yet, which writes and holds that card. The
fold test that claimed an eviction path is renamed.
* fix(native-chat): the queue's pause ignores a Stop or Resume row holding a value no build writes
A Stop or Resume row's value is read from disk with no shape check, and
the pause fold stored whatever it found. A stored `stopEvent: null` would
then throw on every pause check for that chat: the queue's pick, its
send, and every queue update to clients. No build writes such a row, so
this is hardening.
The fold now reads a Stop only when it is an object with a string reason
and a finite time, and a Resume only when it is `true`. Anything else is
ignored: it pauses nothing and ends nothing. The row is still not treated
as malformed, which could cut the history short.
* fix(native-chat): a Stop still reads as yours after Orca restarts before the turn ends
Every stop that ends work now writes the Stop's event before it ends the child: a
person's close of the chat, an eviction (worktree teardown, orchestration stop, tab
cleanup) and the idle sweep's stop of a start that never landed. A stop that ends
nothing writes nothing, and quit writes none: its resume marker records why.
The turn-end write reads the latest Stop event where every turn row is built, so the
adapter's settle, the host's fallback and the relaunch's settle all agree: a turn a
person's Stop or close named, ending with no verdict of its own after that Stop, ends
as their cancellation. A relaunch's probe-bounded end is no earlier than a Stop that
found the turn running. When the provider refuses the interrupt and the turn runs on,
a refusal row answers the Stop, so a later crash still reads Failed; pressing Stop
again after a refusal is a new Stop.
* refactor(native-chat): a stop no longer carries its cause; the turn's end reads the Stop event
The cause of a stop was threaded in memory from each entry through the host's stop
step, the adapter router and each adapter's close onto the `ended` it settled with,
and Claude kept a per-turn copy of a Stop it sent. All of that is gone: adapters
settle a turn they cut as interrupted with no verdict, the host's fallback does the
same, and the one rule where a turn row is built (`turnEndAfterStop`) reads the
journal's latest Stop event to say whether it was a person's.
- `closeSession` / `disposeSession` take no cause; `ended` has no `stopCause`.
- Claude reads an error result after a person's Stop as their cancellation from the
journal's Stop event (through the event sink), not from a per-turn slot, and a
refused interrupt is the host's refusal row, not `withdrawTurnStop`.
- An owed wind-down keeps no cause: its retry's fallback reads the Stop event.
- The mutation context's Stop passes no cause: its step already wrote the event, and
the delivery loop's child-end reason is read back from it.
- A Stop pressed before its turn showed applies to the turn that opens under it,
unless a send a person made since was accepted.
* test(native-chat): a turn a later send opened is no Stop's that named no turn
* test(native-chat): the restart test's death proof carries its detail
* refactor(native-chat): a refused Stop leaves no record; a Stop only ever ends the turn it names
The stop-refused mark is gone: its tombstone kind, its fold, the clock-keyed match that tied it to
a Stop, and the exception that let a second press after a refusal write a new Stop. A Stop that
stops nothing writes nothing. A Codex refusal names a turn that is no longer its active one, and
the Stop names that turn, so the turn running instead never reads as the person's by its id alone.
* fix(native-chat): a Stop pressed before any turn showed stops only the turn opened next
A Stop that named no turn read as the person's cancellation for every later turn that opened
after it, until a send a person made was accepted. The queue's drain, orchestration mail and a
restart continuation send as the host, so a turn they opened long after, cut by a crash, read
"Interrupted" as if the person had stopped it. The Stop now applies only to the first turn
opened after it.
* fix(native-chat): an older Claude's error end after a Stop pressed before its echo reads Interrupted
Claude CLIs before 2.1.91 end an interrupted turn with an error result that names no reason. The
translator judged whether a person's Stop explained it by its own copy of the Stop rule, which
ignored a Stop that named no turn, so a Stop pressed before Claude echoed the send read "Failed".
The translator now writes such an end as interrupted with no verdict and no error row whenever a
person's Stop may name the turn, and the journal's one rule decides as it writes the end.
* fix(native-chat): a person's Stop and /clear each name why they end the agent
The host's mutation path ended the agent with one "recorded" ending for every caller, which read
back the reason of whatever Stop event the journal held last, however old. /clear writes no Stop
event, so its end took an unrelated earlier reason. Each caller now names its own: the chat's Stop
`user-stop`, whose event its own step wrote, and /clear `user-close`, the user replacing this chat.
* fix(native-chat): a host stop judges whether it ends work after the provider's rows land
A close, eviction or host stop decided whether it ended a running turn from the journal as it
stood, while the provider's own rows (the turn its echo opened) could still be in the session's
event sink. A close landing in that gap wrote no Stop event, so the turn it cut read as news. It
now reads after the sink drains, as a person's Stop does, through the same check; a drain that
fails or takes over a second reads working.
* fix(native-chat): a Claude Stop naming a turn that just ended still marks the follow-up it cuts
A phone names the turn it last saw. When that turn had ended and a follow-up was still unechoed,
Claude's Stop interrupted the follow-up and ended the child, but the Stop's event named the ended
turn, so the follow-up's turn the child's end cut read "Failed" under "Cancellation requested.".
A Stop that ends the provider's session ends whatever is in flight, so its event now names the
live turn or none, and a Stop that names none binds the turn opened next. Codex keeps naming only
the turn the Stop names.
The Claude Stop turn-end tests move to their own file, since the session-ending Stop suite is at
its line budget.
* fix(native-chat): the idle sweep reads working by the same rule as a stop's event
The sweep judged a chat resting while a send whose reply was lost was still unanswered, but the
stop's event writer counts that send as work. So the sweep evicted it and wrote an evict event,
which ends a person's Stop pause and let the cards behind it drain on their own. The sweep's owed
work now reads the main agent working the way every session list and the event writer do.
* test(native-chat): an aborted eviction's injected drain failure lands on the eviction's own drain
A host stop now drains the session's sink once to judge whether it ends work, so the tests that
fail the eviction's drain-published step skip that first drain.
* fix(native-chat): the idle sweep's rest writes no Stop event; it evicts a send that never echoes
The previous commit made the sweep count an unanswered send as owed work, which pins a chat whose
admitted send Codex never echoes forever, and the sweep exists to retire exactly that. That rule
returns. The sweep stops only an agent it judged resting, so its eviction now writes no Stop
event, whatever send it retires: a person's Stop pause holds through it.
* fix(native-chat): stopping a start that carries no send writes no Stop event
A host stop, eviction or close of a starting child wrote a Stop event whatever the start carried.
A start with a send already reads working, so the clause only mattered for a start with none,
which ends no turn and no send: its event only lifted a person's Stop pause and bumped the idle
clock, which is why the idle sweep had been changed to close the conversation in the same pass.
The clause goes and the sweep is #24072's again. The child's end still reads host-stop, as before.
* test(native-chat): a Stop's pause across a restart is tested with a restart that writes no event
The rig's restart closes the chat with an eviction, which now writes a Stop event when work runs
and so ends a person's Stop pause. "A Stop never hides a restart's pause" then passed with no Stop
pause left to hide anything. Those tests, and the pause-lift test whose dropped assertion returns,
restart as a process that dies with no close, which like a quit writes no Stop event, and assert
that both the Stop's and the restart's pauses are in force first.
* fix(native-chat): a host stop of a turn a person's Stop is still ending keeps that Stop's reason
An eviction or host stop that landed while a person's Stop or close was already ending the same
turn wrote a newer Stop event, and the turn's end reads only the latest, so the person's Stop of
that turn read as news. A host reason now writes nothing while a person's Stop still decides what
runs: the live turn it names or bound, or, with none, the turn a send opens next. The person's
own close still writes. The E2 tests now open and end the stopped send's own turn, as Codex does,
so the mail turn after it is not the turnless Stop's.
* fix(native-chat): an older Claude's error on a later turn keeps its error text after a Stop
The translator left an error result that names no reason to the journal's Stop rule whenever a
person's Stop named the turn or none, but the rule binds a Stop naming no turn only to the turn
opened next. So a real error on a later turn read "Failed" with its error text dropped. The
translator now asks the journal's rule itself (`personStopDecidesTurn`, the one core
`turnEndAfterStop` and a host stop's in-force check share), so the two cannot disagree.
* fix(native-chat): a Stop of a start that never landed binds no later turn, whatever sent it
A person's Stop pressed while the agent starts names no turn, and the send it stopped is
cancelled before it opens one. The Stop then bound the next turn anything opened (orchestration
mail, a restart continuation, the queue's drain, all of which send as the host), so a host
eviction of that turn wrote nothing and its crash or close read as the person's cancellation. A
Stop that named no turn now binds only a turn no send journaled after it opened: any send since,
of any origin and not refused, opens its own. The E2 test's mail send is accepted as Codex
accepts it, instead of opening the stopped send's own turn first.
* test(native-chat): a rewind's restated turnless Stop binds no turn opened after the rewind
A Codex rewind restates a person's Stop still in force after the turns it keeps, at a new
sequence, so by sequence alone it would bind the next turn opened after the rewind. A send
journaled after the restated row voids that binding (the previous commit), which this pins.
* fix(native-chat): a relaunch settles a person's stopped turn with no "stopped while in progress" row
After a restart, a turn a person's Stop ended reads "Interrupted after N" with the muted mark, but
the relaunch still added the error row saying the provider stopped mid-response, which a live Stop
never writes. The settle now skips that row when every turn it interrupts is the person's Stop's
by the journal's one rule; a crash nobody stopped keeps it.
* test(native-chat): the unexpected-exit settle's journal fake answers whether a person's Stop decides a turn
* fix(native-chat): a host stop whose sink drain fails reads the journal as it stands
A host stop drains the session's sink before judging whether it ends work, and a failed or slow
drain read as working. So an eviction of an agent at rest wrote a Stop event that ended nothing,
which lifts a person's Stop pause, and a close wrote a person's event naming no turn. The drain is
now best effort: the stop goes ahead either way and only its record is at stake, so a failed or
slow drain leaves the journal's read as it stands. A person's Stop keeps its own rule.
* fix(native-chat): a Stop that named no turn applies only to a turn a send it stopped opened
A person's Stop pressed before any turn showed names no turn. It bound the first turn opened
after it, then (
|
||
|
|
975e732ce3 |
feat(native-chat): a solid, fading Jump to latest (#25759)
"Jump to latest" was a faint, see-through pill that popped in and out. It is now a solid button that fades in and out, can't be clicked or tabbed to while hidden, and keeps focus in the composer when clicked. Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com> |
||
|
|
ab6389045e |
fix(native-chat): start Windows chats without reading process creation times (#25718)
* fix(native-chat): start Windows chats without reading process creation times
Native chat on Windows refused to start ("Orca can't run this agent in a chat
here") whenever the process-table addon could not report process creation
times. Chat never needed them; only the bookkeeping around stopping the agent
did.
Windows now follows the common pattern: Stop ends the agent's tree with
`taskkill /T /F` on the child Orca still holds, and reports the tree gone only
when taskkill exits 0. A saved pid is never signalled after a restart.
- Remove the Claude and Codex location gate and the Codex launch refusal.
- A start time that cannot be read is recorded as unknown instead of refusing
the session; recovery already releases such an owner without signalling it.
- Delete Claude's Windows creation-time descendant snapshot and its verifier.
- Codex's Windows teardown reports the real taskkill outcome.
- The renderer no longer waits on the capability flag; the host keeps
publishing it for older clients (temporary).
* fix(native-chat): treat a Windows Claude exit after stdin end as a proven close
On Windows an idle Claude leaves on its own once its stdin ends, so every Stop
and close read as unproven: live background work settled as unknown and the
trace logged a close that "did not finish cleanly". As with the Codex close,
that exit is now the close and Orca makes no claim about processes Claude
started; a forced close still rests on taskkill's own report.
Also drop the Settings clause about Windows needing process start times, and
fix comments and the tracked process-enumeration doc that still described the
removed start-time gate and descendant snapshot.
* fix(native-chat): renew a held child's lease without a PID probe
An owner recorded without a process start time could never renew: the renewer
re-proves every live owner by PID identity, and with no start time and no
spawn-token echo that probe is indeterminate. The lease's last renewal then
stayed at the spawn, so a turn cut by an Orca crash was dated to its own start,
and every tick logged a failed renewal and split the batch into one store
transaction per chat.
The runtime now renews a lease for a child it still holds at the record's
fence and whose exit it has not received, recorded as a `held-child` match.
Receipt of the exit ends that proof before the exit is settled, and a restart
holds no child, so a dead owner's lease still expires and restart adjudication
is unchanged. Records this runtime does not hold keep the PID probe.
Also correct the identity probe's comment about shipped addons and note that
recovery's stop ladder is POSIX-only.
* fix(native-chat): keep a failed Windows taskkill unproven across a retried close
A Windows close counts Claude leaving on its own after its stdin ends as the
close. That shortcut also caught a retried close whose first attempt forced a
taskkill that failed: once the root exited, the retry returned true and the
failure read as a proven close. The tree reaper now records whether a reap
ever reached the live root, here, on an earlier close or from a transport
failure, and the shortcut applies only when none did; otherwise taskkill's
verdict stands and no new taskkill runs against the exited root.
Pin the platform on the existing tests that assume the POSIX close, and say
what `exit-proven` means on Windows in the acquisition-failure docs.
* fix(native-chat): derive a held child's liveness from the adapter's own handle
Lease renewal trusted a held child until the host settled its exit, and the
host hears of an exit late: Claude runs its close ladder and a store write
first, unexpected exits wait on one delivery chain shared by every chat, and a
Claude close that cannot prove its tree publishes nothing at all. A dead root
could keep renewing through that window, so a crash in it dated the cut turn
late. Renewal now asks the adapter, which owns the process handle and sees the
exit first: a child is held only while it is on record at the lease's fence
and its adapter still runs that exact acquisition with no root exit seen. An
adapter that cannot answer falls back to the PID probe. The stored
exit-received mark is gone.
The held-child read is now required by the runtime state and the renewer, and
a host-level test drives renewal through the real host wiring.
|
||
|
|
3268760084 |
Add chat contrast and "Match terminal interface" (#25659)
* Add chat contrast and terminal interface matching * Fix matching chat palettes and contrast controls * Verify rebased appearance settings and preserve slider focus * Memoize chat appearance and narrow settings subscriptions * Wrap native chat tool-run summaries in the terminal font * Use the matched chat foreground for inherited controls * fix(native-chat): bound collapsed tool summaries * Restore code foreground token for matched chat themes * Reset all chat appearance controls through serialized writer * Make terminal matching control chat size and contrast * Verify terminal match controls on Chat settings page * Share native chat appearance store selection * Align matched chat palette test with full-strength foreground * Keep tool-run header marks on the summary's first line A wrapped two-line summary centered the leading icon, failure count, check, and chevron between its lines. The header now top-aligns its items and gives each mark a one-line-tall slot, so marks sit on line 1 and line 2 wraps under the text. * Explain terminal-matched chat controls on hover While Match terminal interface is on, Contrast, Text size and Code text size are disabled. Hovering any of them now shows a tooltip saying the chat is matching the terminal interface and how to change it. Disabled controls drop hover events, so an enabled wrapper carries the tooltip; with matching off the controls render exactly as before. * Name the tool-run alignment test case by summary length |
||
|
|
07981bd12e |
fix(native-chat): chat copy that promised a retry, an update, or a coming change (#25157)
* fix(native-chat): chat copy that promised a retry, an update, or a coming change
- Subagent and background-task rows Orca can no longer settle read "status
unavailable" ("2 agents with status unavailable"), not "no recent update",
which implied an update was still coming.
- /compact or /clear refused behind an unconfirmed earlier message says "Wait
for the agent to finish, then try again." There is no Retry for that message.
- The skills menu in SSH chats says skills can't be listed there; a skill typed
by name still goes to the agent.
- A write the host refuses because this agent or location can't run as a chat
says "Orca can't run this agent in a chat here." plus what didn't happen.
- Spanish gains the chat notice, background-task and subagent rows it lacked.
* fix(native-chat): whole count sentences for background-task states, and translation fixes
- Background-task header counts use one translated sentence per state
(stateWorkingCount ... stateIdleCount), so Spanish and French can agree
the state with the count; English output is unchanged and pinned by a test.
- es: subagent failed/idle/stopped counts and the waiting/blocked reasons
read correctly for any count.
- fr: "listées" agrees with the feminine "skills" in the SSH skills line.
- ja: subagent status-unavailable count uses the 件 counter.
- Correct the refusal-table comment: hostUnsupported is also returned and
recorded, not only thrown.
* fix(native-chat): es/fr background-task counts agree with any task kind
* revert(native-chat): leave the /compact unconfirmed-message refusal to its own PR
Restores main's settleEarlierMessage copy in every locale and drops its comment.
The refusal's behaviour, not only its words, is being fixed separately.
* test(native-chat): expect this PR's Spanish blocked reason in the tool-run language test
|
||
|
|
defd85a9eb |
feat(floating-workspace): render the floating panel through the shared workspace surface (#22302)
* docs(floating-workspace): plan structured native chat in the floating panel
* feat(floating-workspace): resolve the floating workspace so a structured session can run there
* docs(floating-workspace): plan the unification onto the shared workspace surface
* chore(floating-workspace): keep the unification plan out of the branch
* test(floating-workspace): pin the structured route on capability, not workspace kind
The host half removed the 'floating-workspace' blocker; this pin still asserted it and went red
on the merge. Floating now routes like any workspace, so with capabilities un-negotiated the
resolver answers 'runtime-capability-unknown'.
* feat(floating-workspace): resolve the floating workspace's renderer row without a worktree record
The renderer had no Worktree row for the floating id: markdown creation, tab-bar path entry,
native-chat file links, attachments and model discovery all resolve a workspace through
getKnownWorktreeById and silently fail for it. The host already answers with a synthetic row
minted from the resolved floating directory; this gives the renderer the same single seam.
- The repo slice stores the host-resolved floating directory (floatingWorkspacePath); the panel's
existing cwd resolution now writes it there instead of panel-local state, so there is one copy.
- findKnownWorktreeById mints the shared floatingWorkspaceToWorktree row from that path, local
host only, cached while the directory is unchanged — mirroring the folder-workspace branch it
sits beside.
- No consumer changes needed: every row-dependent path already goes through this resolver.
* feat(tabs): scope editor and browser activation to the owning workspace
Shared editor/browser activation always wrote global selection (activeFileId,
activeBrowserTabId, activeTabType). The floating panel is an overlay above the active
workspace, and its active tab must never become global selection — the invariant
documented in agent-auto-ack-targets.ts. Group-scoped activation is a prerequisite for
rendering the floating workspace through the shared tab-group surface.
- setActiveFile and setActiveBrowserTab take an optional target workspace, mirroring
setActiveTabType: global selection moves only when the target IS the globally active
workspace; per-workspace state always updates. Omitted keeps legacy behavior.
- The shared tab-group activation/creation commands pass their workspace, which is a
no-op for the main surface (hidden surfaces are inert, so commands only fire on the
active workspace) and confines floating activation to floating state.
- Terminal setActiveTab was already owner-scoped; now pinned by test.
* refactor(tab-group): separate the workspace drag scope and group tree from main-window chrome
Preparation for hosting the floating workspace on the shared surface, whose shell owns its own
titlebar tab strip and cannot mount the main chrome:
- WorkspaceTabDragLayer: the one dnd-kit scope for a workspace's strips and tree, extracted from
TabGroupSplitLayout as a render-prop layer so a host can put shell-owned chrome (a titlebar
strip) inside the same drag scope as the tree.
- TabGroupSplitNodeTree: the chrome-free recursive group tree, with an explicit tabStrip
composition point on TabGroupPanel ('attached' keeps today's per-group strip; 'external' hosts
render no per-group strip at all — a hidden one would register duplicate drag sortables).
- WorkspacePaneOverlayLayers: the retained pane hosts extracted from WorktreeSplitSurface so any
tree host mounts the same pane lifecycle stack.
- TerminalPaneOverlayLayer gains ownsNativeChatToggleShortcut, following the existing rule that
only one live workspace registers the chord.
TabGroupSplitLayout and WorktreeSplitSurface recompose these parts; the main surface's DOM is
unchanged.
* feat(floating-workspace): decide the panel's emptiness and layout atomically
An empty floating workspace has no tab-group layout at all (the slice starts empty and
hydration skips tab-less workspaces), while the shared group tree requires one. This model
resolves the two together: the panel shows its empty state exactly until the first tab exists,
and mounts the tree only with a layout plus a live focused group (stored focus, else the group
with an active tab, else the first group — matching the shell chrome's focused-group policy).
* feat(floating-workspace): render the floating panel through the shared workspace surface
The panel previously projected exactly one tab group and hand-rendered its panes, so any tab
that left that group — 'Move Tab to Split' creates a second group — stayed alive in state but
vanished from the panel. The body now mounts the shared group tree and retained pane overlay
stack for the floating workspace id, so every group renders, splits work, and structured chat
finally has a floating surface.
- One drag scope: WorkspaceTabDragLayer wraps the titlebar strip AND the body tree, so exactly
one DndContext owns floating tab drag; strip drops reorder, body-edge drops split. The
floating-only FloatingWorkspaceTabDragContext is deleted.
- The titlebar keeps the panel's single TabBar, now fed by the shared per-group workspace model
of the focused group (tabStrip='external' keeps per-group strips out of the shell), with
workspace-scoped activation commands so global selection never moves.
- Panes render through WorkspacePaneOverlayLayers: terminals gate on the resolved cwd and a
settled viewport exactly as before; browser guests ride the shared retention layer; agent
session tabs appear in the strip and render through the structured overlay.
- The empty state renders exactly until the first resolvable tab exists (stale unified entries
still show it), per the atomic layout/emptiness model.
- Deleted: the single-group projection, the hand-written pane maps, FloatingBrowserSlot, and
the floating pane-handle registry.
Known behavior deltas, both narrowing floating onto main-workspace semantics: Cmd/Ctrl+W with
panel (not terminal) focus closes the active tab rather than one pane of a multi-pane tab, and
a pane-owned close of the last tab no longer arms the panel focus reclaim (strip and shortcut
closes still do).
* test: re-register moved pane-ownership sites with the extraction
The retained overlay stack moved into WorkspacePaneOverlayLayers and the floating chrome model
became a guest-paint consumer; the structured-pane layering pin and the browser-guest retention
census both name files, so they follow the seam.
* fix(floating-workspace): mount the shared group tree in a flex frame so panes own their height
The tree's nodes size themselves as flex items, and the floating shell mounted
them in a block container, so every group body — and every pane anchored to
one via anchor-size() — measured 0px tall. Wrap the tree and overlay layers in
the same 'absolute inset-0 flex' frame the worktree split surface uses, record
the host contract on the tree, pin the frame in the panel's element-tree test,
and add a real-renderer e2e spec that measures group-body and pane boxes for
the single-group and split layouts (jsdom computes no layout, which is how the
0px regression passed 50k tests).
* fix(runtime): import the floating worktree minting the structured create path calls
floatingWorkspaceToResolvedWorktree called floatingWorkspaceToWorktree without
importing it. The module is @ts-nocheck (mechanically split runtime), so tsc
never saw the missing import and it only failed at call time — the structured
createSupport probe for the floating workspace threw a ReferenceError. Add the
import and a test that runs the real method, which fails on the missing import
where the typechecker cannot.
* fix(browser-pane): resolve floating and folder workspace paths in notebook navigation
navigateBrowserPageToUrl looked the workspace up in allWorktrees(), which has
no row for folder or floating workspaces, so their notebook navigations ran
with an undefined workspace path. Use getKnownWorktreeById, the same resolver
every other consumer was moved to.
* fix(tabs): keep a workspace's tab selection out of the main window's global selection
* refactor(floating-workspace): launch and close panes like every other workspace
* refactor(tab-group): let the surface that mounts tab groups describe its own chrome
* refactor(floating-workspace): render the floating panel through the shared workspace surface
* fix(floating-workspace): resolve the floating directory once on the host, and keep the controls' inset
* test(floating-workspace): pin the unified floating panel against the shared surface
* fix(floating-workspace): read the floating directory's paths through the app environment
The runtime now resolves the floating directory itself, and the headless
runtime has no Electron app, so the Electron path lookup threw there.
* fix(floating-workspace): keep the main window's frame and shortcuts out of the floating panel
The shared layout drew the main window's 4px titlebar band and sidebar seam
inside the floating panel, and the chat-view toggle fired in every workspace
on screen, so one key press flipped the main window's tab and the panel's.
* fix(floating-workspace): close floating tabs without taking over the main window
An unsaved floating note's save prompt made the floating panel the main
window's active worktree, and never appeared on a fresh launch because the
workbench that owns the prompt was not mounted. The prompt now shows a
floating note in the panel, and the workbench mounts while the panel holds
tabs.
Every close path now reads the same captured emptied-workspace reaction, so a
floating pane closing itself, its shell exiting, a group close, and an unsaved
note close all keep the panel's keyboard ownership, as a tab close already did.
* test(tab-group): pin that a dirty editor close carries its emptied reaction
* refactor(tabs): one rule for when a selection moves the main window's selection
Opening a file kept a floating-only exception while every other writer checked
whether the tab's workspace is the active one. Every writer now asks the same
question, so opening a file in a background worktree no longer points the main
window's editor selection into that worktree.
* refactor(floating-workspace): drop what the shared surface left unused
The terminal pane's imperative close handle lost its last consumer when the
floating panel stopped keeping its own pane registry, and the floating and
folder workspaces built the same lineage-free resolved worktree twice.
* test(floating-workspace): type test fixtures without casts and drop the removed pane ref
* fix(floating-workspace): keep panel focus when a close lands after the panel emptied
Saving the last floating note closes it after an awaited write, so the panel
could render empty before the close armed its keyboard reclaim, leaving the
intent armed with nothing left to consume it. The panel now re-checks whenever
the intent arms as well as when its tab count changes.
A pinned tab's confirmation took focus before the close read panel ownership;
the dispatcher now captures it before any prompt.
* refactor(floating-workspace): finish what the shared surface left behind
- The floating panel no longer accepts AI vault session drops: a session
cannot resume into it, and its window-level drop handler fired beside the
main window's.
- The last global-selection writers that spelled the active-workspace check
inline now use the shared rule.
- Remove the save-dialog hook and item counter the old floating body used, and
correct comments that said the workbench only mounts once a workspace exists.
* fix(floating-workspace): keep a floating leaf detach from changing the main window's tab type
* fix(floating-workspace): give floating chats their workspace identity and directory
* fix(floating-workspace): read one visibility fact for chat attention and auto-ack
* docs(floating-workspace): correct comments the shared surface made stale
* fix(attention): one on-screen rule for every tab's unread marker, main window and floating panel
* fix(agent-session): pin the folder a session launched in; floating chats resume there or refuse
* fix(native-chat): resolve a floating chat's file links, images and skills in its pinned folder
* fix(agent-session): open a floating chat's terminal handoff in its pinned folder
* fix(agent-session): surface refusal text on chat open, require the launch-directory rule, and keep bookkeeping from gating a launch
* fix(floating-workspace): wait for a floating chat's pinned folder and read panel visibility from the store
* test(floating-workspace): assert the toggle event without a cast
* fix(native-chat): show why a chat could not be opened instead of dropping the host's refusal
* fix(floating-workspace): close chat tabs through shared commands
* fix(i18n): remove obsolete floating editor loading key
* fix(floating-workspace): preserve tab ownership on activation and browser close
* fix(floating-workspace): keep terminal activation and focus reclaim in sync
* fix(readme): point translations at tracked media
* fix(floating-workspace): retain split surface with an empty focused group
* docs(tabs): describe workspace-owned activation accurately
* fix(floating-workspace): finish the main merge and name the pin a launch directory
Port what the merge left on main's newer shapes:
- The floating chat's "folder is gone" refusal becomes a typed reason,
launchFolderMissing, worded like every other start refusal (copy, desktop
words, five translations). Older clients drop the unknown reason and show
the code's generic words.
- The PR's launch-folder tests run on main's journal-database record store;
the attach-level case is a row in the pre-spawn refusal table.
- Tests main added since the PR's base learn the PR's close-queue settling,
workspace-scoped notebook lookup and tab-stamped chat host.
- Move the store's visible-tab-index reads into the tab table, so the record
store stays under the line cap with the pin writer in it.
- Drop a duplicate Worktree import the merge left in a @ts-nocheck file.
Rename the persisted field, the status summary field and the renderer slice
from workspacePath to launchDirectory: it records the folder a session first
launched in, not its workspace's path, and it has not shipped yet.
* Fix floating chat visibility, closes, and directory pinning
* fix(i18n): drop the floating save dialog's strings with the dialog
The floating panel now closes through the shared unsaved-changes prompt, so
its own dialog's six strings had no caller and failed the runtime-catalog
check.
* fix(native-chat): pin floating founders and retain pinned editors
* test: align auto-ack fixtures with unified tab visibility
* test: give the forget-status attach fixture a location
Attach now reads params.location to decide whether a new record is a floating founder, so the partial fixture needs the location real attach params always carry.
* test: type the forget-status attach fixture instead of casting it
The cast tripped the changed-code type-assertion gate.
* fix(native-chat): read a floating chat's model defaults in the folder it was created in
The model picker read the floating setting's current folder, so a chat
pinned to another folder could show that folder's project default.
* test(cross-version): give the stub host the record store the catalog read now checks
* test(claude): give main's thinking-display launch stub the store shape the resolver takes
|
||
|
|
468e4e1167 |
fix(native-chat): a prompt card owns the chat input until its answer lands (terminal-backed chat, desktop and phone) (#25761)
* fix(native-chat): an answerable prompt card owns the chat input until its answer lands * fix(mobile): a terminal chat's composer waits while its prompt card is up * test(native-chat): type the prompt card fixtures without casts * fix(native-chat): scope replies to acknowledged prompt occurrences * fix(native-chat): preserve answer ordering and verified delivery * test(native-chat): keep mock RPC client inside test boundary * test(native-chat): place mock fixtures in the test-only scope * Keep runtime comments within the module size limit * test: preserve prompt delivery coverage in desktop CI * Treat an older host's accepted write as delivered A newer desktop or phone talking to a host that predates the write settlement field read every accepted reply as "unconfirmed". Prompt cards never dismissed, the phone showed "Response unconfirmed" on every tap and ordinary chat messages were held as "Delivery unconfirmed". The reader now uses writeSettlement when present and otherwise keeps the host's whole-write accepted/refused verdict, exactly as before this branch. Only prompt answers ask for provider settlement; ordinary callers (follow-up delivery, paste drafts, option commands, composer sends) are back on the original contract, so the legacy-handoff error class, its flag, the sequence-only send helper and the mobile handoff hook are gone. * Keep terminal-pane Escape on the plain accepted write Every pane's Escape/Ctrl+C goes through pty:writeAccepted. This branch had switched that IPC to wait for provider settlement, which dropped the "remount this pane" signal for a daemon session awaiting recovery and could stall later keystrokes behind a slow daemon acknowledgment. pty:writeAccepted is back to its original local-only, synchronous write. Prompt answers opt into settlement with requireWriteSettlement on the same channel, and a settled refusal while the daemon recovers now sends the same remount signal. Ordinary verified sends regain their original fallback write. * Report a partly accepted local paste as unconfirmed A settled local write split into chunks returned plain false when a later chunk was refused after earlier ones were accepted. Callers read false as "nothing was written", so chat showed "Message not sent" with a prefix already in the agent's input. It now reports the write as unconfirmed, the same verdict the paired host gives for a partial write. * Hide the chat composer under a prompt card instead of unmounting it When an approval or question card took the input region, the composer unmounted. A message still waiting for its Enter was cancelled and its bubble deleted after the draft had already been cleared, so the message vanished without a notice; composer history was also wiped each time. The composer now stays mounted but hidden while a card owns input, so its state survives. A send that has not submitted yet is still stopped (its Enter would answer the card), but its bubble stays with "Message not sent" so the text is not lost. The composer ref is detached while hidden, so root typing, paste and reveal focus never reach it. * Keep an answered prompt hidden after the chat view remounts The "answered" dismissal lived in component state. Toggling chat to terminal and back, a PTY reconnect, or leaving the phone session and coming back while the approved tool was still running brought the answered approval back, and it then took over the input again. Desktop now keeps the answered occurrence per pane outside the view; phone keeps it per chat tab outside the controller. Both still retire it when the pane observes the prompt clear or change, desktop also when the tab retires, and both maps are size-bounded. * Update the prompt-reply reliability gate for the review fixes Older hosts' accepted answers now dismiss like acknowledged ones, the composer stays mounted under a card, and answered prompts survive a view remount. The gate's invariant, oracle, assertion list, new test files and the two latest evidence runs now describe that contract. * Let users hide a prompt card, keep Escape from denying, and gate only Send on the phone The chat input could stay locked behind a card the host never closes (for example after a Deny typed in the terminal), and Escape on a focused approval card denied the tool even when the user meant to close a picker. - A Hide control (chevron) on terminal approval and question cards, desktop and phone, hides that prompt occurrence and gives the input back. It writes nothing to the agent and uses the same per-occurrence dismissal as an acknowledged answer, so a new occurrence shows the card again. - On desktop, Escape on a card now does the same Hide instead of Deny, and a card that appears while the user is typing no longer takes focus. - On the phone, a card blocks only Send: typing, dictation and image attach keep working on the draft. The placeholder is back to the normal one. * Fix two comments that still called older-host replies unconfirmed Since an older host's accepted write now counts as delivered, the requireWriteSettlement comment and the reliability gate's oracle said the opposite of the code. Both now describe the current rule. * Collapse prompt cards to a strip instead of hiding them, and close the round-2 gaps Hide removed a card completely, so nothing on screen said a prompt was still waiting, and several edges let the chat type into a live prompt. - Collapse (the header chevron, or Escape on desktop) folds the card to a one-line strip above the composer; the strip's chevron expands it back. Collapsing writes nothing, frees the composer, and is disabled while an answer is still being written. Each pane or tab keeps the occurrence as answered or collapsed, so a remount restores the same view. - Questions now carry the host wait's start like approvals, so an identical question in a new wait shows again (desktop and phone). A transcript-only prompt, which has no wait start, is dropped when the view stops observing it, and a transcript still loading no longer clears a dismissal. - Desktop: while a card owns the input, the hidden composer cannot send or interrupt even if it still has keyboard focus, and the card takes focus in the same commit. A send the card retires no longer types Ctrl+U under it. - Phone: an Ask hides the heuristic card read from the same waiting status, and the dismissal store is scoped by host, worktree and tab. * Keep a collapsed card's partial answer, and scope its focus to its own pane Collapsing a question card unmounted it, so expanding it again lost the chosen step, selections and typed "Other" text; Escape typed in that text field collapsed the card. A card arriving while the user typed in another surface (sidebar, notes, a browser URL bar) also took the keyboard. - The collapsed card now stays mounted but hidden (and inert on desktop) under its strip, on desktop and phone, so a partial answer survives collapse and expand. Escape inside the card's text field no longer collapses it. The question card shows the same focus ring as the approval card. - A card takes focus only from inside its own pane (its hidden composer) or from the page body, never from a text field elsewhere. - Desktop and phone share one dismissal store in src/shared, bounded by the existing scope-cache helper, which moves to src/shared with it. - The card send imports the verified helper from its own module, and the phone files are split so each name matches its contents (header action, strip, lane selector). * Return focus to the composer after a prompt card collapses Since a collapsed card stays mounted, Escape or the chevron left keyboard focus inside the now hidden, inert card. The composer's reveal-focus took that as focus already in the pane and stood down, then the browser dropped focus to the page body, so typed keys went nowhere. Reveal-focus now treats focus inside a hidden or inert subtree as not in the pane and focuses the composer. On the phone, collapsing a card also dismisses the keyboard so a hidden reply field does not keep it. * Keep the question card's collapse chevron beside its Cancel button The question card header spread its three items with justify-between, which put the new chevron in the middle of the header. The title now takes the free space, as in the approval card, so the chevron sits next to Cancel at the right edge. * Run the prompt tests on the merged main Main now runs Vitest under Bun, which resolves a long data: URL import as a package name, so the SSH delivery test loads its bundled mobile module from a temp file instead. The phone prompt harnesses mock the live line that main's view now renders, and add Platform, which main's text-selection helper reads, the same way main's own view tests do. |
||
|
|
ccab2a3110 |
fix(native-chat): a long reply keeps its Working bar and Stop after a reload, from the host's turn (#25751)
* fix(native-chat): the chat reads whether a turn runs from the host, not the loaded history page * fix(native-chat): the Working bar, card cancel and phone Stop follow the host's turn when its rows are not loaded * fix(native-chat): keep the phone's Stop as it was, naming the host's running turn Drops the conversation-wide phone Stop: the phone shows Stop only once a turn is running, so a Stop naming no turn could not be reached and only changed the ordinary Stop's request. Stop and card cancel still name the turn the host says is running. * fix(native-chat): the host's turn answer never outlives its host, and a half-loaded turn shows no edit totals - A batch carrying rows restates the host's newest turn record, so one without it (an older host after a downgrade) drops back to the loaded rows instead of holding the last answer; the event coalescer merges by the same rule. - A turn whose record is above the loaded rows is grouped only while it runs, and its loaded tail gets no "N changed files" total, which would undercount. - "Thinking" reads the host's running turn too, so it shows when the turn's record is not loaded. - The wire doc for observedAt says what the journal stores: creation time. * refactor(native-chat): totals per turn come from their own hook, keeping the transcript list under its line limit Also drops the chat zoom prop main removed from the unloaded live turn test. |
||
|
|
842c6c667a |
fix(jira): tolerate malformed nested Jira status fields (#25656)
* fix(jira): reject malformed nested status data * fix(jira): tolerate malformed status fields * fix(jira): tie status parsing to the shared types Allowed auth types and credential protections are coverage records over JiraAuthType and SecretAtRestProtection, and the parsed status must list every JiraConnectionStatus key, so a union or field change in the shared types fails tsc instead of being silently dropped by this parser. Adds a populated multi-site round-trip fixture and keeps connected statuses with a malformed viewer or sites value connected. * fix(jira): require every site and viewer field in status parsing Site and viewer parsing now lists every JiraSite and JiraViewer key, like the top-level status, so a new optional field fails tsc instead of being dropped. |
||
|
|
cad8ea4fa7 |
fix(tabs): stop tab and close tooltips firing while grazing the strip (#25808)
* fix(tabs): delay tab and close tooltips past grazing
Tab title and close-icon tooltips fired near-instantly while dragging
across the strip: Radix's provider-wide skipDelayDuration reopens the
next tooltip with no delay for 300ms after any tooltip closes.
Scope a nested TooltipProvider to the tab strip with a 700ms delay and
skipDelayDuration=0, so each tab tooltip waits the full delay.
* fix(tabs): drop close-button tooltip and lengthen tab tooltip delay
Remove the hover tooltip from the tab close button entirely (aria-label
keeps the accessible name), and raise the tab-title tooltip delay from
700ms to 1500ms so grazing or scrolling the strip no longer pops a label.
Add an interaction test proving the delay is enforced and Radix's
skip-delay is disabled.
* fix(tabs): delay the agent-state glyph tooltip to match the strip
The tab's leading agent-state dot renders its own feature tooltip with a
hardcoded 200ms delay, so tabs with a live agent state (working/done/
needs-attention) popped almost instantly while plain tabs waited the full
strip delay — the 'every other tab' inconsistency. Thread an optional
delay through StateIndicatorTooltip/AgentStateDot and pass the tab-strip
1500ms from TerminalTabLeadingIcon; other surfaces keep 200ms.
* fix(tabs): remove the agent-state glyph tooltip on tabs
Hovering a tab's status glyph (spinner/check/question) popped its own
tooltip after 200ms, so tabs with a live agent state popped while plain
tabs stayed quiet. Remove that tooltip entirely via title={null}; the
glyph stays self-describing with its aria-label, and the tab title keeps
its tooltip. Reverts the now-unused delay plumbing.
* reduce the time down from 1500 ms to 500 ms
* fix(tabs): restore the close-tab shortcut hint with a slower delay
Close buttons share one TabCloseTooltip that waits 1s, longer than the 500ms tab-title tooltip. Also merges the tooltip provider tests.
|
||
|
|
7faa9f7cd3 |
fix(native-chat): reasoning rows with a real open/finished state, and readable Claude thinking (#19221)
* fix(native-chat): render structured reasoning as collapsible messages
* fix(native-chat): align expanded reasoning with summary
* fix(native-chat): place reasoning chevron after summary
* De-emphasize reasoning headlines with observed timing labels
* Exclude later turn work from observed reasoning duration
* fix(native-chat): give reasoning rows a host-owned open/closed lifecycle
A reasoning row now says whether its block is still streaming (`state`)
and, when the host saw it end, when (`completedAt`). The row's own
start is its first-write time, so "Thought for N s" is measured on the
execution host instead of by whichever window happened to be watching.
Rows open with their first non-empty text and close on every path that
ends them: the block's final frame, a new message in the same stream,
every Claude turn end through one hook on the open turn, Codex
item/completed, turn and session settlement, active-item eviction, and
both host sweeps for a dead generation. Closing writes are lifecycle
writes so backpressure cannot leave a row open. Rows without the field
(older hosts, older journals) never read as live.
The reasoning row's headline reads the row's lifecycle and its own
turn's liveness: Thinking while open in a running turn, then Thought
for N s, Thought when no span was seen, Reasoning when the host kept no
lifecycle.
* feat(native-chat): ask Claude for readable thinking summaries
Under Orca's launch the Claude CLI streams thinking blocks with empty
text, so no reasoning row ever had anything to show. Pass only
`--thinking-display summarized`: it fills thinking blocks with the
API's summaries without turning thinking on, so a user who disabled
thinking keeps it off.
Orca runs the user's own binary, and a CLI older than the flag exits on
it before the session starts. The launch probes the binary it is about
to run, overlapping the rest of launch resolution, and passes the flag
only when that probe has already answered with 2.1.94 or newer. A slow
or failed probe never delays a launch and is never remembered; a
successful one is kept per binary until the binary changes.
* test(native-chat): type the superseding send in the reasoning lifecycle test
* fix(native-chat): stop reading an ended reasoning row as thinking
The spinner line infers "Thinking" from the newest root row being a
reasoning message. With summaries on, a closed reasoning row stays the
newest row while Claude streams a tool's input, so the line read
Thinking for the whole Write. A reasoning row now counts only while its
own state is running; a row from a host that keeps no state reads as
before.
* fix(native-chat): measure reasoning from the block's start, not its first text
A row opens with its first summary text, which trails the thinking
block's start by seconds, and the journal stamps a row with its queued
append time. So "Thought for N s" read 1 s for blocks that ran 4.65 s
and 12.95 s. The block registry now records the host time at each
block's content_block_start (else its first delta), and every write of
that block's row carries it as the row's observed time; the close keeps
the frame's own receipt time. Codex reasoning rows likewise carry their
item/started time.
* perf(native-chat): stop rewriting the reasoning row for every thinking token
Claude sends a thinking_tokens frame after every thinking delta. Every
frame the stream path did not consume forced the streamed text out
first, bypassing the checkpoint widening and the coalescing window, so
a single thinking block was rewritten and republished once per token:
212 full-row writes and 258 KB for one captured block. A frame that can
write no row (token tallies, stream deltas no registry carries, pings)
no longer forces that flush; every frame that can write still does.
The same block now takes 14 writes.
* fix(native-chat): time Codex reasoning by receipt and close evicted rows
Codex summary text lands 13-50 ms before item/completed, inside one
coalescing window, so a reasoning row's first write can be its
completion. Item boundaries are now stamped with their host receipt
time the way turn boundaries already were, so a retried or buffered
delivery keeps it, and the item translator falls back to the host
clock rather than Date.now(). The row carries its item/started time on
every first write, including the completion, so its span is
item/started to item/completed.
An evicted active item is now closed from the text streamed so far,
like both settle paths, and the eviction runs before the incoming item
is tracked: tracking first let the stream bound drop the evictee's text
before the eviction could close its row, stranding it running.
completedAt now has one meaning everywhere: the host time the message
was seen to end, or the end of the turn or stream that cut it off;
absent only when no end was seen live.
* refactor(native-chat): keep the Codex streaming body translation pure
The streaming translation preserves a reasoning body as-is again; the
stream writer, which is what knows the item has not completed, stamps
it running.
* test(native-chat): keep a re-collapsed reasoning row collapsed through a revision
* fix(native-chat): estimate a collapsed reasoning row as its trigger
A reasoning row renders collapsed, as one small button, but its height
was estimated from its full text: a 4,129-character summary reserved
about 950 px for a 24 px row, so long chats jumped as rows were
measured. It is now estimated at the trigger's height; opening the row
remeasures it.
* fix(native-chat): probe the CLI the launch will run, and learn from a refusal
The thinking-display gate probed `claude --version` with Orca's own cwd
and env, while the launch spawns with the workspace's cwd and the shell
env. Behind a version manager's shim those can pick different CLIs, so
the probe could approve a CLI the launch never ran, and an older CLI
exits on the unknown flag before the session starts. The probe also
only counted if it had already finished when resolution did, so a
first launch, or the first after a CLI update, usually went without
the flag.
The probe now runs with the launch's own resolved cwd and env, is keyed
by the binary and the workspace, and a launch waits up to 200 ms for it
(about 3x the probe's measured p95) before going without the flag. Only
answers are kept, so a slow or failed probe is asked again next launch.
A child that exits with commander's "unknown option '--thinking-display'"
marks that binary in that workspace so the next launch skips the flag;
that one start fails exactly as any CLI startup failure does today.
* test(native-chat): type the thinking-display probe mock with both of its parameters
* fix(native-chat): recheck the account switch after the probe, last as before
Moving the invocation ahead of the probe, the transcript check and the
permission mode put its account-switch recheck before those awaits, so a
switch that began during them launched unchecked. The invocation is the
last await again. The probe gets its own env from the same sources the
launch uses, the inherited env and the overlay with the CLI's runtime on
PATH, built by the same code, minus every credential: asking a CLI its
version needs none.
* fix(native-chat): wait up to 1.5 s for a cold probe, and remember every outcome
200 ms only covered a warm CLI; a cold disk, a node install or an
antivirus scan exceeds it, and that chat's child then ran its whole
life without summaries. A launch now waits up to 1.5 s, once per binary
per workspace, measured from when that binary's probe began, so a later
launch never waits again on a probe already past it. The probe gets its
own 10 s kill timeout, and every outcome, including no version printed,
a failure or a kill, is kept for the binary's life, so a probe that
hangs costs one launch rather than every one. A refusal seen while a
probe still runs wins over its late answer.
* fix(native-chat): leave Thinking to the activity line while reasoning runs
A reasoning row still being written drew a pulsing "Thinking…" header
right under the turn's activity line, which already says Thinking: two
live indicators for one fact. In a running turn an open reasoning row
now draws nothing and reserves no height; it appears when it closes, as
"Thought for N s". A row from a host that keeps no state, a closed row,
and a row left open by a turn that ended draw as before. The row's
Thinking headline is gone with its catalog key.
* fix(native-chat): end every unfinished Codex item through one rule
A reasoning completion with no text of its own left the row its stream
wrote running for good: the completion translated to nothing and the
item left the active set, so no settle could reach it. It now closes
from the text streamed so far.
Settlement, eviction and that completion now build an unfinished item's
row through one choice (the streamed text when there is any, else the
item as it started) and end it through one rule. An evicted file change
with streamed tool output no longer keeps that output as its patch; it
reads as interrupted, as a settled one does. A completion whose start
was never recorded claims no span, so it reads "Thought".
* fix(native-chat): catalogue Claude's stream keep-alive as benign
An uncatalogued `ping` stream frame classified as substantive, so the
fallback wrote a visible "claude · message:stream_event:ping" row, and
since such frames no longer force streamed text out first, a ping
inside a coalescing window landed above the open reasoning row. A ping
is now benign: it writes no row.
* fix(native-chat): bound finding the binary by the probe budget, and keep it LRU
Resolving the command's real path and its mtime was awaited before the
budgeted wait, so a slow filesystem could hold a launch indefinitely;
it now counts against the same budget, and running out caches nothing.
The cache is least recently used rather than first written, and holds
32 binary-and-workspace entries rather than 16.
* feat(mobile): collapse reasoning rows the way desktop does
With summaries on, every Claude turn now carries reasoning text, and the
phone drew all of it inline, dimmed, between the prompt and the answer.
Mobile now draws a reasoning row as desktop does: collapsed to "Thought
for N s", "Thought" or "Reasoning", its text mounted only once opened,
and nothing at all while the row is still being written in the live
turn or has no text. The headline and the visibility rule live in one
shared module both clients read, so they cannot drift.
* fix(native-chat): let a failed CLI probe heal instead of latching
A probe killed at its timeout, failing to spawn under a loaded boot, or
printing no version was cached as "no flag" for the binary's life, so
that workspace never got summaries again in that run. Only a version
(either side of the floor) or the CLI's own refusal is kept for good
now; a probe that gave no version is kept for 10 minutes, so a hung CLI
still costs one wait per stretch and a boot-time failure heals.
* docs(native-chat): say exactly what the version probe's env leaves out
* fix(native-chat): record a Codex item's start whatever its first frame carried
The start was recorded only for an item/started that wrote no row, so a
reasoning item that started with text lost it and its completion
claimed no span. Every tracked item/started now records its receipt
time, and the started write carries it too.
* fix(mobile): label the reasoning toggle and give it a full touch target
The toggle now tells a screen reader what it is, "Reasoning: Thought
for 3s", as desktop's prefix does, and reaches a 44 pt target. The
shared English copy stays private to the module that formats it.
* refactor(native-chat): build reasoning rows from one provider-neutral helper
Claude, Codex and the terminal sweeps each built the reasoning row body and
its running/ended stamp themselves. They now share journal-reasoning-row:
blank text journals no row, text is bounded the same way, and an end carries
completedAt only when the host saw it.
* refactor(codex): move the active journal item type into the contracts file
codex-unfinished-item-body imported the type from the settlement module,
which imports values from it.
* test(claude): read the launch PATH the way Windows spells it
* test(claude): compare the probe's PATH to the launch's without Orca's CLI dir
When the CLI's directory also holds node (Linux CI's /usr/local/bin), the
runtime pairing puts that directory first, ahead of the Orca CLI directory the
launch adds, so the launch PATH no longer ends with the probe's. Both still
resolve the same claude and shims. The test now checks that exactly, for a CLI
with and without a sibling node.
* feat(native-chat): lead the reasoning row with a brain glyph in the tool-row column
* fix(native-chat): route the reasoning glyph through the shared icon names, keep its chevron findable, and match it on mobile
* fix(mobile): keep the long-press actions sheet on reasoning rows for Android
* fix(native-chat): forward every exit argument through the thinking-display connection wrapper
* refactor(codex): keep the receipt-timed notification methods with the event they stamp
* feat(native-chat): read an open reasoning block through the one live "Thinking" line
While the agent's open reasoning block has text, the turn's live activity line is its
disclosure: collapsed by default, expandable to the live text (capped and scrollable), and
the block's row draws nothing meanwhile. When the block ends, its row appears in place,
open if the reader opened it live, because the line and the row read one disclosure key.
Which block the line discloses is derived from the line's own render condition, so a row
is never hidden while nothing on screen shows it; any other open block (a subagent's, or
one a prompt pushed off the line) draws as "Reasoning". Desktop and mobile alike; no host
or wire change.
* fix(native-chat): a slot kept for its turn bar or diff rollup no longer draws its message
The transcript row drew the message of every message slot, while the slot builder pushes a slot
for a row it declined to draw whenever that row also carries its turn's bar or diff rollup. So the
open reasoning block the live line discloses still drew as a "Reasoning" row when it was a
provider-opened turn's first row or the last row of a turn that changed files, and one click
opened both. The builder's decision now travels on the slot (`drawsMessage`) and the row draws
only the bar and rollup when it is false; the row-level `folded` guard it made redundant is gone.
* refactor(native-chat): draw the live line from one shared value, with one live region
Desktop and mobile now render the live activity line from one pure function,
`nativeChatLiveLine`: whether it draws, what it says, and the open reasoning block it
discloses with the text it has so far. The open block's row is hidden from that same value,
so desktop no longer restates the line's render condition beside it, and the lines no longer
re-derive the text.
The line keeps one element, and so one live region, through every state; only its trigger
and body come and go, so a screen reader hears "Thinking" and the label after it. On mobile
the live text gets the finished row's Android long press (copy or select through the message
actions sheet), the line's touch target is the row's 44 pt, and its label and body sit in
the finished row's column so nothing moves when the row takes over.
* fix(mobile): keep the live text's actions sheet on the block it was opened for
On Android the sheet opened by a long press on the live reasoning was a flag gated on a live
block: it vanished when the block ended, mid Select text, and the stale flag reopened it
unprompted on the next block. The sheet now holds the message it was opened for.
* test(native-chat): the reasoning body owns its tone, live and once landed
Rendered QA on a pre-merge build showed the live line's open reasoning in full foreground and the
landed row's in muted text, so it dimmed as the block landed: the body set no colour of its own and
inherited one from wherever it was mounted. Since the main merge (
|
||
|
|
5cf3585b78 |
fix(native-chat): keep a message accepted before a quit or crash as a held card (#24660)
* fix(native-chat): keep a message accepted before a quit or crash as a held card A send the host accepted while the agent was still starting, and never handed over, was rejected unseen at quit or at the next open after a crash. The next open now keeps a person's message (typed, or a launch's first prompt) as a waiting card at the head of the queue, held until Resume, Send now, Edit or Delete; quit no longer rejects it. Each submission records its source so a restart knows which leftovers to keep. A direct send's replay answers from its own record, never the queued arm. Cards shown without the queue capability hide Turn off queueing and the steer chord. * fix(native-chat): keep an unsent message as a held card at every close, not only a restart The host now keeps a person's message it accepted and never handed over with one rule wherever it can no longer hand it over: a quit or crash (settled at the next open) and a close of the chat (tab close, worktree teardown, orchestration stop). - The hold is card state: a new per-card hold_reason 'kept', published as the existing pausedReason, instead of a fake host_instance value. host_instance means the owner again, and the pause clause, adoption filter and /clear carry special cases are gone. A kept card holds the cards behind it until the person sends, edits or deletes it; a send_failed card still does not. - A card hand-off rejected by a restart or a close returns as a kept card (rejectedDraftSettlement), so a person's next message can never release it. - One hold function, parameterized by cause (hostRestarted / chatClosed), replaces the close path's plain rejection. - The phone shows published cards and per-card holds whatever the queue capability says; only queueing a new send stays gated. - source gains 'dispatch' for the orchestration preamble (still rejected); an unknown source is kept as written and never takes the legacy rule. - Kept cards from earlier settlements stay ahead of a batch's new ones. * test(native-chat): the dispatch preamble records its source * fix(native-chat): skip a kept card like a failed one, and make a quit leave the queue as a crash does - A kept card is held on its own, as a send_failed one is: the queue sends the cards behind it, and the "a message ahead needs attention" caption no longer appears behind it (desktop and phone). - Quit disposes the queue's drain together with delivery, so it mints no hand-off that only the next process could settle. - Only a Send the person asked for (origin client) that a restart or close cut short returns kept; the queue's own hand-off returns where it stood, under the restart's pause, as on main. - Comments that said only a capable host gets cards or the card actions now say the capability gates only queueing a new send. * refactor(native-chat): one dispose gate in the queue drain's step * test(native-chat): the downgrade test names the older build's /clear exception * fix(native-chat): re-check the drain's quit gate right before it appends a hand-off A drain step already past its first check when quit begins no longer makes a hand-off. Adds regression tests for that, for Send now on an ordinary card cut short by a quit, and for the open-time repair deriving kept from the hand-off's origin; fixes the pause and settlement comments that called a kept card one the queue never passes. * fix(native-chat): a card held on its own starts no restart pause for the others After a second restart a kept (or send_failed) card is another process's, but it waits for its own Send, so it no longer pauses every other card under "Queue paused because Orca restarted". * fix(native-chat): record on a kept send which card holds it, so no trace survives an Edit or Delete The rejection row of a send the host kept as a card now names that card (`keptAsQueuedMessageId`), in the same transaction that writes the card, and the fold publishes it on the submission. The shared projection draws such a send only as its card: once the card is sent, edited or deleted, neither the send nor the sending desktop's local copy of it shows. * chore(native-chat): leave the unused submission schema as main has it No client parses published submissions with it (they arrive as typed frames, and the host's history pages carry the field, as the Edit/Delete test reads); listing the field put the file over its line budget. * fix(native-chat): retire a kept send's local copy instead of only hiding it The outbox reconcile and the send disposition drop an entry whose submission the host rejected as kept as a card, as they already do for a Stop's withdrawal, so the copy never comes back as "Not sent / Retry" once the submission falls out of the loaded page. The projection reads the reconciled outbox, so its separate filter goes. * test(native-chat): move the queued-message rig's scripted provider into its own fixture The rig fixture grew past the 300-line limit once main's changes merged in. * fix(native-chat): hide a kept send by its own record, not by its card still existing The transcript hid a rejected send while a queued card held it under its id, so the card's Edit or Delete brought back a "Not sent" row. It now reads the send's own keptAsQueuedMessageId, which the host records with the rejection, and the live card list is no longer threaded to the transcript or the delivery notices. * refactor(native-chat): move a sent message's row writes into their own journal collaborator The journal store went past its line limit once main's ledger receipt joined this branch's transaction hook. The submission and dispatch-transition writes, and what commits in their transaction, now live in JournalSubmissionWriter; the store's methods delegate to it unchanged. * fix(native-chat): list a kept send's card id in the submission schema The schema drops keys it does not list, so a reader that kept a parsed submission would lose keptAsQueuedMessageId and source, both persisted with the row. The submission schema and the failure fact it shares with item bodies move to their own modules, with room for both fields. * test(native-chat): match the transcript and outbox hook signatures main and the swap changed * test(native-chat): import the journal types once in the queue-delivery test * fix(mobile): a resend the host kept as a card shows no error and returns no text The host answers a resend of a message it kept as a card with that message's rejected submission, marked keptAsQueuedMessageId. The phone read it as any rejection: "Message not sent" and the text back in the composer, while the card showed the same text. It now answers like a queued send, as the desktop's send disposition does: the id is spent, no error, and the card holds the text. * test(native-chat): pin that quit's first step stops the queue's hand-off Quit now stops delivery, the queue drain included, at its first step (stopDelivery), before teardown drains recovery. The drain-step quit test runs from that step as well as from the flush. * test(native-chat): give cards their source and store unknown sources as another build would #25078 made a card's source required, so the tests that insert a card pass the person's. The hold's unknown-kind and unreadable-source cases now rewrite the stored row the way a newer build would leave it, instead of casting a type. * refactor(native-chat): stop delivery and the queue drain in one line at quit Main's #25159 left the host at its line limit; quit's stop now disposes both in one expression instead of a block. * test(native-chat): hold the drain step without reading the call stack Bun formats a method's stack frame without its class ("at step"), so the quit test's caller check never matched, the step was never held, and both cases timed out once CI ran Vitest on Bun (#25840). Only the drain step heals owed queue bookkeeping, so the hold needs no caller check. |
||
|
|
b75213100b |
Add a Chat settings page for structured native chat (#25685)
* Add a Chat settings page for structured native chat * Remove unused chat appearance summary translations * Fix chat settings search entries and preview anchor * Keep shortcut formatting available in settings sidebar tests * Clarify the Chat settings page description * Update Chat settings test and remove unused summary alias |
||
|
|
3ec38b8c6d |
Run Vitest on Bun with Node runtime contracts (#25840)
* Run Vitest on Bun while preserving Node runtime contracts * Preserve runtime timing provenance and keep the Bun pin in config * Scope builtin compatibility mocks to test-only lint exceptions * Give capture retention fixtures distinct filesystem timestamps * Await the copy button success state in the React fixture * Bound Node test worker shutdown and tighten migration fixtures |
||
|
|
5b8a982f8f |
Fix private Linear images in task descriptions (#25849)
* Fix private Linear images in task descriptions * Strengthen Linear image signing validation and typed access |
||
|
|
768c1ea967 |
Add confirmed conversation rewind from structured chat messages (#19338)
* feat(native-chat): confirm destructive conversation rewind from user messages * fix(native-chat): retain rewind uncertainty from host status * fix(native-chat): await the authoritative rewind epoch reset * Pause restored chat outbox during rewind recovery * Verify rewind targets after message acceptance * test(native-chat): verify rewind copy against host reason contract * fix: type rewind hook test props explicitly * Show rewind explanations on keyboard focus * fix(native-chat): read renamed background stop-all capability * fix(native-chat): consolidate epoch test vitest imports * fix(native-chat): restore rewind refusal delivery after merge * fix(native-chat): satisfy main's design-system and cast gates in rewind Match the rewind button to the copy button beside it instead of restyling the shared Button, read refusal reasons without a cast, and merge a duplicate import. * fix(native-chat): let the host govern an in-doubt rewind; offer Edit from here only on turn openers - Pending starts after Confirm and ends with the request; a confirmed rewind waits up to 120 s for its new conversation, then lets go with one toast. - An unknown outcome no longer blocks sending: the host recovers it on the next send. Its latch only disables the action. The message goes back to the composer on success and on an unknown outcome; refusals and timeouts are toasted once instead of holding the error line. - The action is offered only on sent prompts that opened their own turn (no steers, unsent, queued, /compact or goal rows), and not at all where the provider cannot rewind. - Rewind is held behind host-queued cards, gets the command's 195 s remote timeout, focuses the composer after returning the message, drops the message count, and uses plain copy. * fix(native-chat): say why a send waits while a rewind is in flight * fix(native-chat): settle an in-doubt rewind on the next send, even with the agent running - Host: a send (or /clear, /compact) that finds a rewind in doubt with the agent already running now runs the same provider recovery an attach would. Proven: the conversation is replaced and the send proceeds; proven not done: the record is refused and the send proceeds; still unknown: the send is refused as before, but before the ledger records it, so a Retry of the same id is decided afresh instead of replaying the refusal for a day. - Client: a confirmed rewind whose pane was hidden during the wait lets go silently (a hidden pane reads nothing); the timeout and busy copy say what happened and what is needed. * fix(native-chat): keep /clear's settled refusal while a rewind is in doubt; type the live-send test fixture * test(native-chat): give the live-send rewind host the declared agents main now requires * fix(native-chat): offer Edit from here only where the next send settles a rewind, and never on URL images - New runtime capability agent-session.rewind-send-recovery.v1, advertised by this host: the rewind UI hands an in-doubt prompt back for the next send to settle, which hosts with only the rewind RPC refuse while the agent runs. The action is hidden until the host (local or remote) advertises it, and while that is unknown. - A prompt with an image that has no local file is not offered: the image could not go back to the composer after the rewind discards it. - Row and provider eligibility move to native-chat-rewind-eligibility.ts. * fix(native-chat): keep when each kept message was first seen across a rewind A rewind rebuilt the provider's kept items with the rewind's own time, so every kept message showed the rewind time. The merge already carried a held row's scope and producer onto the provider item; it now carries its first-seen time too, for the rewind and its recovery alike. * fix(native-chat): hand rows the rewind through context so the list and pane stay within their line budgets after main's appearance changes * fix(native-chat): fit the rewind recovery capability within protocol-version's line budget; hoist a test context value * fix(native-chat): label the action "Rewind to here" --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
d9cf07d3f4 | Fix pinned workspace reveal expanding other hosts (#25836) | ||
|
|
ee917205bd |
fix(native-chat): a chat you return to drops background tasks that finished while it was hidden (#24305)
* fix(native-chat): a chat you return to drops background tasks that finished while it was hidden A background command that finished while its chat pane was hidden stayed in the strip as running, its timer counting for hours, and Stop failed with "The background task wasn't stopped." The pane stops listening while hidden, so it missed the "no tasks" update; once the idle sweep stopped the agent, the host reopened the pane's subscription without any roster at all, and the client reads a missing roster as "unchanged". The roster now rides each subscriber's frames the way the slash-command list and the message queue already do. The subscriber registry reads it from the host's child records on a subscriber's first frame and on every snapshot and reset, and re-sends it to each subscriber whose last copy differs when the records change. The channel always answers: a conversation with no running child is "none" whether or not an agent holds it. Ordinary journal batches never read it. This also fixes the reverse case: a snapshot or reset sent to a live pane carried no roster, which cleared a still-running task from the strip until the roster next changed. The client treats a resumed subscription's first batch as stating the roster, so a pane reconnecting to an older host that still omits it drops its stale copy too. Fixes #24227 * refactor(native-chat): require the background-task roster read and drop the unused close observers The host's delivery layer now requires every hook it is built with, so production wiring cannot leave out the roster read (an opening frame without it reads as "no tasks" to current clients). The conversations map's close observers lost their only caller in this PR and are removed. The reducer test comment states the old-host omission rule precisely. * refactor(native-chat): keep structured-agent-session-host.ts within the line limit Main brought the host file to the 300-line lint limit, and this PR's roster wiring adds one line. Name the lease reconciler's type by its factory, as the neighbouring fields do. * test(native-chat): build the roster test's Claude provider handle with claudeProviderHandle Main made the provider handle opaque (#24991); build it with the existing helper, as main's own tests now do. |
||
|
|
634c43787c |
Fix premature Claude automation completion and inherited CI failures (#24878)
* fix(agent-status): keep a Claude pane working until owed task wake-ups arrive Claude wakes the main agent for each background task that ends, after the task stops running. "Nothing running" was read as done, so automations closed the terminal before the final turn. Fixes #23942 * fix(agent-status): keep owed task wake-ups through failed turns, the cap and nested exits A failed turn's task list now marks a vanished shell owed like a normal turn end does. Past the cap only an already-announced sub-agent is forgotten. A process exit clears what is owed only where the server admits it from the pane's owner. * refactor(agent-status): move pane-scoped cache entry helpers out of listener-state listener-state.ts passed the 300-line limit once main's and this branch's additions met. * fix(agent-status): verify Claude task wake-up completion on its execution host * fix(agent-status): canonicalize Claude background task identifiers * Preserve out-of-order Claude task notification evidence * Verify retained released parser in cross-version checkout test * Reuse watcher directory-cache enumeration without losing fresh keys --------- Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
a2f197fde7 |
Refresh visible reviews automatically and stop settled merged polling (#25788)
Refresh only actual on-screen review cards and the selected visible review panel, with a single metadata owner per execution host. Open reviews refresh every 60 seconds when selected and 120 seconds otherwise. Settled merged reviews stop automatic refresh; pending merged checks continue, hidden rows stop, and work changes or stale re-exposure discover fresh state. Remove the polling setting. Preserve provider/SSH/runtime ownership, mixed-version cache behavior, failure backoff, bounded foreground admission, request coalescing, and mutation invalidation. Cover older persisted caches and unknown HEADs. Validated with 1,628 focused tests, typechecking, lint/code-quality gates, hidden Electron viewport checks, and parallel Codex/Claude Opus 5.5 adversarial reviews. Measurements and policy details are in PR #25788. Fixes #25746 Co-authored-by: ggbdpq <ggbdpq@gmail.com> |
||
|
|
c4ea14cd9f |
Show a tool call you stopped as interrupted, not failed (#25181)
* Move the turn message ordinals and the turn-row revision to the neutral timeline folder Pure moves so a shared timeline assembler can use them: Codex's message ordinal counter becomes ProviderTurnMessageOrdinals and Claude's turn-row revision becomes the provider-neutral agent-journal turn-row revision. Only names and import paths change. * Admit one provider event's writes as one transition, and let rows be found again after a restart - A sink transition is admitted whole or not at all; its steps run back to back at their turn in the journal's write queue, and each resolver reads the fold with every earlier write landed. A resolver may also say where the row belongs (turn scope, provider reference), and the writer always hears how the transition landed. A resolved lifecycle batch chooses its settlement mutations from the fold at execution. - New optional row field providerItemRef: the provider's own reference for the item a row is, written only where the row's identity cannot spell it (Codex keys messages by their place in the turn and renumbers its item ids on resume). Set by the creating write, kept by revisions, indexed by the journal fold, never read by clients. A downgrade test shows an older host and client render such rows unchanged. - Provider timeline identity schemes (shared legacy arm, Codex) and the join index that resolves a provider item to its row from memory or the fold: ordinals and request incarnations are read back from the rows, so a restart or an evicted entry finds the original row instead of placing a new one. * Recover message ordinals from the journal's highest place, and forget joins read from a replaced epoch A fresh join index continued a turn's messages at the first free place, so a journal holding only a later ordinal (an imported or removed earlier row) had its sequence back-filled. The place is now one past the highest ordinal any row or echoed send holds there, read through a pure scheme reader. The join caches also drop what they read when the journal's epoch is replaced. * Spell the subagent thread's message slot without spreading an identity union * Add a provider timeline grammar and a shared assembler that decides at its turn in the journal Adapters translate their provider's dialect into a small grammar (turns, items, streamed text, requests, context facts, session end/reset); one shared assembler turns it into the journal rows every structured lane writes. Each event is planned as one sink transition. Which row a write lands on, whether a replay writes anything, and every change to what the assembler knows (its ledger) are decided by the transition's resolvers at the event's turn in the journal's write queue, against the fold as it stands then. A forecast (the ledger plus admitted events still queued) only answers apply() at once. So a refused event allocates nothing, a write the journal rejects leaves no trace in memory, and a restart or evicted cache finds the same rows again. Text and full snapshots of one provider item share one row and one lifecycle; reset always flushes text and settles the old session from the journal; the open-work budget is derived from what is actually open. Codex migration contracts compare against the existing Codex translator, including a restart mid-stream and a repeat that outlives the join cache. * Fix the types and the exhaustive event switch CI reported for the assembler * Let the journal decide stream lifetimes, request reuse, named sends and background work A third review found two blockers with the earlier rounds' cause, a remembered interpretation trusted after the journal moved on: - A reused request id was judged by its earlier prompt's settled turn before asking which turn the new one lands in, so a real approval in a later turn was dropped. The target turn now decides: the old turn again is a replay; a different live turn opens the next prompt beside it. - A text stream checked its row's turn only on its first write, and a turn's end released streams by the turn planning expected. Every write now checks the row, a turn's end stops the streams whose rows are in it, and turn status reads the journal first, so another writer's Stop wins. Also: a message boundary drawn by an event the journal held as a replay no longer splits an anonymous message; a send naming a turn not yet open waits for that turn; the budget charges a stream's thread and turn strings and the turn caches are byte-bounded; the open turn ends when the journal shows it settled; a turn's opener is read from the journal's row. Background work is now Orca's existing background-task row instead of a tool call flagged `outlivesTurn` (a flag remembered only in memory, so a restart failed the task). A turn's end never settles that row, so it survives restarts; session end leaves one in flight unverifiable. Three tests that opened a background tool call with `outlivesTurn` now open a background-task row and keep their original expectations about which turn the row stays in. * Type the unbound assembler helper's drain as the void it reports * Bound the rows kept for a continued anonymous message and the stopped streams A row kept for the anonymous stream that may continue it, and the marker that a stopped stream's queued writes write nothing, lived in the live-stream map and were never removed when no stream followed. They now live in their own bounded maps, so the live map holds open streams only. * Read a tool call a stop or the session's end cut short as interrupted, not failed A call still running when its turn was interrupted, when the provider child was seen to exit, or that the provider cancelled now carries `endedAs: 'interrupted'` beside `state: 'failed'`. Every build that predates the field keeps reading the call as the failure it always showed; this build reads both through one shared reader and counts the call apart from failures in the run header, without the error tint on its partial output. The shared assembler, the restart settlement and the Codex lane settle a running call through one rule: only a proven interruption cuts it short, so a turn the provider completed around an unclosed call, or work the host lost track of, still reads failed. * Assert a completed Codex turn leaves its unclosed call plainly failed * Assert an unverifiable restart settlement leaves its running call plainly failed * Let a later death proof correct the calls an unverifiable settle closed A settle with no proof of the child's death closed its running calls as plain failed, so a proof written afterwards corrected the turn to interrupted but could not find the calls. The call now keeps endedAs: 'unverifiable' beside its failed state, and the stale settle revises exactly those calls whose owner the proof names. Older builds still read them as failed. * Render a cut-short call's output on mobile, with and without proof * Find the mobile result box by walking to its View, and type the unverified-ending block * Walk to the mobile result box without a type assertion * Keep the journal store under its line limit after the main merge * Drop the provider item reference, join index and identity schemes from the transition PR Nothing in production reaches the state they defended (an assembler that lost its memory while its child keeps streaming the same turn), and the stored Codex id was positional. The legacy identity scheme moves to the assembler PR with its first caller; the Codex scheme and any persisted reference wait for Codex to move onto the assembler. The Codex ordinal counter goes back to codex/, since no neutral code imports it. * Write a resolved settlement in one transaction through enqueueRows A settlement too large for one row now commits all its rows or none, through the journal's existing all-or-nothing write, instead of a row-by-row writer. Every row is built before any commits, so a settlement naming one item twice is refused before anything is written. * Drop the transition's landing report; keep the turn-row write fire-and-forget Nothing reads which steps of a transition wrote. A failed step fails the sink, leaving the steps before it written; the header says so, and tests cover it plus a settlement whose second row fails inside the transaction. writeAgentJournalTurnRow returns nothing again, as on main. * Run a transition's steps as a prefix; drop the paced flag and resolved options A failed step no longer lets the steps after it write: each step checks, at its own turn in the journal's queue, whether the write handed over just ahead of it completed, using the queue's count of completed write bodies (a promise would report the failure only after the next step ran). The sink fails only once every step has had its turn. The item step's `paced` size bypass and the resolver's replacement `options` are removed; nothing planned uses them. * Rebuild the timeline assembler on one admission-order state The assembler kept a second copy of its state (a forecast beside a ledger), hydrated the open turn from the journal, and recognised replays, all to recover from losing its memory while the provider child kept streaming. That never happens: one assembler lives exactly as long as one child, and a new child is a new assembler in a new generation whose events land after the dead-generation sweep. - One state, changed only when the sink admits an event (minted keys included), so a refused event takes nothing. - Every journal-dependent choice is made when the write runs, by keyed reads: a turn row is written only where none is, a stream checks its row's turn on every write, a running snapshot never lands in a settled turn or relights a settled tool, a request takes the first incarnation the journal holds no row for. - Rows are found by spelling their ids (provider-timeline-rows.ts); no join cache. - The identity scheme (legacy arm only) lives here with its first caller; requests are spelled in their acquisition generation, since JSON-RPC ids restart per process. - Saved history goes in as `input.history` plus ordinary events with the provider's ids, into an empty journal; `session.reset` and every replay rule are gone. - One terminal-body function (`terminalAgentJournalBody`) is shared with the dead-generation settlement. - The test rig's restart now sweeps and starts a new generation, as production does. * Cover new running work in a turn the sweep ended * Run a transition's steps in one queued write that loops over them The steps of one event now share one turn in the journal's write queue: a loop writes each in its own transaction through the row writer's synchronous writeRows (split out of enqueueRows) and stops at the first throw. Prefix semantics and "nothing lands between the steps" now hold by construction, so the completed-write counter on the queue, the step gate and the allSettled barrier are gone; the queue is back to main's bytes. * End a turn another writer settled the way the provider's end does A person's Stop settled the open turn's row without a word to the assembler. The assembler then forgot the turn: its running tools and pending prompts were never settled, the provider's own end and withdrawal were dropped, and the turn's text streams stayed counted against the open budget for the life of the process. Text the provider kept streaming afterwards could land as a message outside the stopped turn. - The open turn the journal shows settled ends first, as one transition, through the same settlement the provider's turn.end plans; its streams stop and their keys drop later text until that turn's end or the next turn opens. - turn.end and request.withdrawn are admitted for a turn or request the journal holds; their settlement writes nothing for rows already settled. - The budget's re-check frees streams whose turn settled. - A settled tool keeps its terminal body against any differing write. - Session-end settlement of lost background work uses the journal's own lostLiveWorkJournalBody instead of a copy. - The rig's window elapses before every read, and restart swaps and disposes the old assembler. * Leave a stopped turn's running tools to the agent's own end When another writer settles the open turn (a person's Stop), the assembler now only stops that turn's text and cancels its pending prompts. Running tool calls stay the agent's: a progress update or completion it reports after the Stop lands as reported, and whatever is still running settles at the agent's turn end for that turn, the next turn's open, or the session's end. An agent's end for an earlier turn while a newer one is open no longer clears the open turn's activity line or ends its anonymous reply. An unnamed end right after a Stop ends the stopped turn instead of being dropped. The test rig's restart no longer writes the dead assembler's window text, matching dispose. * Pin that a stopped turn's running tools hold budget until the agent's end * Type the stopped turn's tool progress update as a tool body * List every event the assembler hands to the decision step The type-aware lint requires an exhaustive switch with no default case. Also retitle a Stop test to say what it asserts. * End a running call as its turn's journal row ends A call still running when its turn ends takes the state of that turn's row: a row another writer settled first (a person's Stop) stands, so its calls read interrupted whatever the provider's later end reports. The no-ending path that settled calls from the Stop row is gone, since a Stop now leaves running calls to the provider. Adds the two Spanish strings. * Settle a stopped turn's running call as its turn row ended after a restart too The restart sweep ended every running call by the death evidence alone, so after a person's Stop with no proof the child died the call read failed under a turn that read interrupted. The sweep and the live dead-generation settlement now ask the same rule the assembler does: a call in a turn already settled ends as that row ended; only a turn still running leaves its calls to the evidence. * Keep the dead-generation settlement under the line cap * refactor(native-chat): drop saved-history adoption from the timeline assembler The common pattern discards the history a provider replays while loading a session, so the assembler has no use for an input.history event. * refactor(native-chat): a pending input is only Orca's send now Review follow-up to the adoption removal: drop the comment naming the provider's saved message, and make requestedAt required since every pending input comes from input.accepted. * test(ratchet): require src/main/provider-process now that it has landed * test(native-chat): build this stack's journal identities with main's opaque provider handle Main's #24991 replaced the {kind, ...} handle with {transport, agent, nativeId}; three test files from this stack still wrote the old shape. Same lines the downstream ACP branch uses. |
||
|
|
6a9ba9d733 |
Keep remote worktree creation from navigating paired clients (#25793)
* fix(workspaces): keep remote creation from navigating paired viewers * fix(workspaces): revoke pending navigation after connection replacement * fix(workspaces): provision when the host renderer is unavailable * test: align navigation and cache-scan expectations with current behavior * fix(workspaces): provision broadcast creates without a host renderer |
||
|
|
1de3aa405f |
Fix truncated Unicode branch names in base ref search
Preserve complete branch selectors when Git truncates or disambiguates short names, and keep slash-named local branches separate from remote-tracking refs through worktree creation and reuse. Keep native/SSH namespace boundaries and mixed-version client capability gates intact. Accept Git-valid dotted components and cover collisions with configured and orphan tracking refs. Adapted from @nishino-tsukasa's #19540. Independently reviewed in seven adversarial rounds; 235 focused tests and 77 affected-Apple-Git tests pass, along with typecheck, quality checks, and the desktop build. Correct the inherited file-explorer cache-scan expectation while retaining its exact constant-work bound. Correct the inherited release-parser compatibility assertion to match the pinned test dependency; all 121 cross-version tests pass locally. Fixes #19515 Co-authored-by: kino <nishinotsukasavirgo@gmail.com> |
||
|
|
ac3a25ca37 |
Wait for Codex's live composer before pasting linked issue drafts (#25779)
* Wait for Codex's live composer before pasting launch drafts * Keep timed-out Codex draft delivery consumed across remounts * Document the checked terminal fixtures used by the remount test * Distinguish Codex's reserved footer row from early multiline input * Complete transcript baselines and suppress hidden terminal strings * Align watcher scan budget with linked-directory reconciliation * Drop unused snapshots after main removed the readiness census |
||
|
|
f9c8cd4fc3 | Reduce redundant test coverage and unnecessary CI waits (#25806) | ||
|
|
635e7f53cb |
feat(github-projects): render Board project views as a kanban with drag-and-drop (#19074)
* Add board layout support for GitHub project views Board-layout views render as a kanban. Columns come from the view's verticalGroupByFields (the host retries without the selection on older GHES schemas and the renderer falls back to the Status field), with one column per single-select option in option order — empty ones included — plus a trailing no-value column whose drop clears the field. Card drops reuse the table's field mutation path, committed from a document-level capture listener because the preload's native-drop bridge stops drop events before React's root ever sees them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(github-projects): harden board capability probe and drop lifecycle Review follow-up. The verticalGroupByFields capability probe now matches parsed GraphQL error messages instead of substring-scanning the whole response body — partial-error responses echo the field name as a data key on healthy schemas, so one SAML/FORBIDDEN partial error could permanently degrade github.com boards for the session. Covered by new project-view-config tests per the capability-cache testing contract. Also: only single-select/iteration vertical fields shape columns (a drifted field kind no longer yields a clear-on-drop no-value column), optimistic patches resolve the column field through the board/group config, drag cleanup moved to a document-level dragend listener, the column dot follows dark mode via the chip CSS variables, the supported- layout allowlist is a single shared predicate, and the board's edit handler is stable so its drop listener stops re-registering per render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(github-projects): serialize board edits and verify rendered drops * fix(github-projects): preserve refresh baselines and order edits across views * fix(github-projects): accept source settings projection in cache scope * fix(github-projects): distinguish view switches from refreshed field baselines * test(github-projects): type board IPC recordings and verify clear request --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
ca4e239861 | Remove low-value test inventories and duplicate fuzz oracles (#25791) | ||
|
|
dc8b3934b2 |
Add chat appearance settings: text size, code size, width (#25657)
* feat(native-chat): add chat-scoped color tokens * feat(native-chat): soften transcript and composer appearance * fix(native-chat): refine code spacing and faint text styling * fix(native-chat): wrap prose links at word boundaries * test(native-chat): refresh background task strip snapshots * Add native chat appearance settings and card scaffold * Persist chat text and code sizes with appearance width controls * Fix chat appearance typing, shortcut routing, and scoped typography * Use chat call identities in typography regression fixture * Keep markdown metadata typography independent of chat text size * Preserve terminal undo chords and stabilize chat resize estimates * Show only primary shortcuts in chat appearance settings * fix(native-chat): preserve appearance edits and configured zoom shortcuts * Remove unused chat appearance summary translation * Preserve command classification when applying chat code size |