mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 08:01:56 +00:00
e6fbbdf6840686f79c00a7239300742a455477bc
892
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0de5e4d84d |
Fix terminal focus when Cmd+J wakes a workspace (#23546)
* fix: retain workspace terminal focus through wake restoration * test: reset CPU throttling after wake focus assertion * fix: require terminal textarea readiness before claiming focus * test: configure React act environment for dialog regression |
||
|
|
7a24d3d335 |
fix(native-chat): the conversation outlives its agent (#22835)
* refactor(native-chat): remove the unused terminal handoff No client ever called agentSession.requestHandoff or mounted the handoff chrome. Delete the handoff coordinator, the terminal-owner runtime, the proof write path and the unmounted UI. Keep agentSession.handoffStatus, which released desktop clients read for worktree activation, and let records an older build left mid handoff reconcile through the ordinary restart and recovery paths. * fix(native-chat): never let the pre-stop snapshot hold a chat's stop Eviction now drains delivered events before quit's resume-offer snapshot. An unbounded wait there sits ahead of the provider stop, so a sink whose journal write stalls kept the child running until the step deadline aborted the eviction. The offer is advisory: bound the drain and stop the child regardless. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop helpers only the terminal handoff called `claudeAuthEnvCarriedForward`, `isPathWithinDirectory` and `queryWindowsProcessRowsFresh` lost their last caller with the handoff. The fresh-scan tests now go through `queryWindowsProcessDescendants({ fresh: true })`, the teardown path that still depends on that contract. Co-Authored-By: Claude <noreply@anthropic.com> * docs(native-chat): stop citing the removed handoff in lifecycle comments Six comments still named the handoff coordinator, a handoff suspend, or a terminal-owned session as live participants in the flows they describe. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stalled snapshot drain without a cast Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that a start dead before proving owes no settlement The removed restart handoff test pinned this branch; nothing else did. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): keep the owner-status read behind an in-flight attach The handoff removal dropped the per-session queue from `handoffStatus`, so a read landing mid-start reported the reservation (no owner) instead of the settled chat owner, and shipped desktop clients blocked worktree activation on it. The read is queued again, as it was before the removal. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(terminal): remove the agent-session PTY write gate The gate only refused a write when a PTY had been bound to a chat session, and the only code that ever bound one was the terminal handoff this branch removes. With it gone, every admit/readmit returned "admitted" unconditionally, so the checks on the renderer write path, the runtime controller backstop, terminal.send, agent prompts, preview input and orchestration pointers, the refusal fields on terminal.send and worker-start receipts, the plugin and CLI refusal copy, and the adopted-pane orchestration routing could no longer run. Ordinary writes take the same path in the same order as before. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop the transcript helpers only the handoff called appendLegacyTranscriptMessages fed the terminal transcript catch-up and proveClaudeTranscriptBranch backed the terminal owner's exit proof. Both lost their last caller with the handoff. Their tests now go through the live entry points instead: the roster bounds through the legacy import, the pinned-read and growth tests through the ancestry replay the history window uses, and the marker rules through the string proof in their own file rather than the session-file resolver's. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): stop calling a starting chat "mid-handoff" A send refused because the chat's owner is not settled showed "The session is mid-handoff (<stage>)." in the composer. With the handoff gone, the stages that reach it are a chat that is still starting, or one whose previous agent process has not yet been confirmed stopped. The message now says which of the two it is. The refusal code is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stand-in roster decoder without a cast Co-Authored-By: Claude <noreply@anthropic.com> * refactor(codex): name the pinned rollout lookup for what it does With the terminal handoff gone, the module named codex-tui-rollout-proof holds only the pinned rollout lookup that structured Codex launches use to resume a thread, so the name described code that no longer exists. Rename the module and its options type. Also drop a mobile allowlist assertion that pinned the removed agentSession.requestHandoff method, which no longer exists to allow. * refactor(native-chat): type the owner-status reply as the host sends it The handoffStatus reply type still listed the terminal handoff's fields and states (terminal placement, host label, proof retry, queued and waiting phases, the to-terminal direction). No host writes them any more and the only client reader parses the reply as unknown, so they described nothing. The reply on the wire is unchanged. * refactor(native-chat): normalize terminal-handoff lease values once at decode Nothing in this build writes a terminal owner (`runtimeKind: 'tui'`) or the handoff's `preparing` / `old-owner-stopped` stages, but the in-memory types still admitted them, so readers across the host kept branches for values no path produces and the compiler could not point at them. The store now validates the on-disk shape, which still accepts those values so an older record is not quarantined, and maps them once while parsing: - `preparing` and `old-owner-stopped` become `recovering` - a `tui` lease becomes `native`; when it records a process it also becomes `conflicted`, the claim every build probes but never stops. A plain native owner would be stopped by restart recovery, here and in older builds. Revisions are taken over the normalized state on both sides of every compare, and the mapped record reaches disk with the store's first transaction, the same way the tab-id backfill does. The in-memory types narrow to what this build writes, and the branches that existed only for the removed values go. Structured-worker identity keeps its verdict for a former terminal owner by refusing a conflicted claim rather than a non-native kind. * refactor(native-chat): stop threading the owner kind through a reservation A reservation only ever names a native owner now, so the request no longer carries a kind and the reserved lease records `native` directly. The attach params keep `runtimeKind`: agentSession.ensure and create accept it, and the operation fingerprint stored in the ledger covers it. * test(native-chat): pin the legacy-lease rewrite with a transaction that changes nothing else Hiding a tab also committed the visibility index, so the no-op transaction wrote the file even when its open-time revision was wrong. Committing the index first leaves the pending rewrite as the only reason to write. * fix(native-chat): name a chat write by its target, not the owner generation A write carried the fence of the last frame the pane read, and the host refused it unless that fence was still current. An idle release and the restart after it each move the fence, and the release publishes nothing, so a send after a release was refused "Expected runtime fence 1; the session is at 3", and a Stop queued behind a cold start was refused as stale. Every write already names what it acts on: a send its conversation, a cancel its turn, a prompt answer its item revision, a rewind its epoch; an option is last-writer-wins. So admission stops comparing the client's fence, and the rebase that papered over one restart (admitAtResumedFence, resumedFromFence) goes with it. The writer-lease check stays, and so does the attach's compare-and-swap. Frames now stamp the fence read when each frame is sent instead of a copy each subscriber kept, which went stale on the same release. * fix(native-chat): every journal append reaches the chats that are open A journal write and its delivery to open readers were two calls, and some writers made only the first. A failed start whose lease could not be handed back, a provider revision with no frame behind it, and eviction's settlement were all journaled without reaching an open chat. A journal handle now reports every durable change, and the host's session map binds that report to the session's readers when the handle is set. Writers no longer publish what they append; the per-writer publish calls are deleted. * test(native-chat): an epoch replacement reaches the open chat * test(native-chat): each row reaches an open chat once, and a live handle enters only through the map * test(native-chat): give the legacy-lease store test a tab id so the backfill cannot supply its rewrite The seeded record had no surface tab id, so the next open backfilled one and that rewrite alone made the no-op transaction write. The test passed with the legacy-lease rewrite signal removed. * test(worktree-activation): restore the OMP surfaced-agent resume test The handoff removal deleted it alongside the terminal-owner tests, but it covers the surfaced-PTY block that still guards resume, including an agent whose ownership is unknown. * perf(native-chat): a publish behind a delivered commit reads nothing Each commit now delivers itself, so the publish a provider frame still sends afterwards found every reader caught up but still read rows and rebuilt the timeline for each one. A caught-up reader now skips the read. * test(native-chat): state why the teardown test's fake journal is safe to cast * docs(native-chat): say mutation admission checks only the writer lease * docs(native-chat): drop the send rebase from comments that still described it * fix(native-chat): a message is accepted, then delivered A send to a chat with no running agent restarted the agent inside the send call, before the message was recorded, so the client waited for the whole start and a failed restart refused the message. Claude held prompts sent during startup, and those could settle as "unconfirmed". A send is now accepted inside the session's serialized queue: one ledger row and one submission row marked handoverRecorded, published, answered pending. A per-session delivery loop exists while a message is queued. It starts the agent through the same serialized attach a hold uses, waits outside the queue for a Claude child to prove its start, and hands the oldest queued message over as its own serialized step, writing dispatch{pending} before the adapter call. A start it needed and did not get writes one error-tone row and rejects every queued message with the same words; a start Stop cancelled writes none. Settlement follows from the rows. A queued message is provably unwritten, so a close, an eviction or an exit rejects it. A handed-over message stays in doubt. A queued row at or below the sequence a handle found when it opened was left by an earlier process and is rejected at open, with no latch. Stop withdraws queued messages with no writer lease and no fence. An attach failure keeps the conversation open, and the attach adopts its journal. Owed work counts the loop and queued rows. A compaction or rewind found prepared when a conversation opens was started under a child this process no longer has, so the open settles it rather than leaving it to refuse every send until a view attaches. The open cursor is scoped to its epoch, because sequences restart when an epoch is replaced. Deleted: restart-before-admission, recordFailedRestart, the fence rebase, Claude's startup gate, the attach's forget on failure and its own crash boundary. Clients without agent-session.accepted-send.v1 get their reply held until the handover; the desktop and paired desktop lists advertise it. * fix(native-chat): settle queued messages only for the child that ended A child that proved its start and then exited before its message was handed over left the message queued: the exit settlement returned early when nothing else was in flight. Delivery then started another child for it, and a child that died the same way started another, without end and without a row. A retried settlement for an earlier generation, run by the attach that delivery started, did the opposite: with that generation's turn unfinished it rejected the message queued for the child being attached. The settlement now takes the rejection for queued messages from its caller. The unexpected exit and the eviction pass one, and it applies even with no other work in flight; the retry for an earlier generation passes none. * fix(native-chat): an adoption that fails to import keeps the conversation open The attach now writes into the conversation's own open journal, but a failed transcript import still closed it as if it were the attach's provisional one. The conversation stayed indexed with a closed journal, so every later send answered "could not be recorded" and every attach failed again until the app restarted. The import now closes only a journal the attach opened for itself. * perf(native-chat): the recovering open reads the journal once Every conversation open now goes through the recovering open, including the read restore of every chat at startup, which used to replay its journal once. The recovering open replayed it twice: once to probe it and again inside the open. The probe is now handed to the open as its load. * fix(native-chat): an attach that fails after indexing its child leaves no child behind A failed attach now keeps the conversation open, but a failure after `onAttached` indexed the child (the rewind or compaction recovery, or the attach's own success record) left that entry claiming a child the failure path had already released. The next send found the phantom, skipped the start, and wrote at a fence the journal had moved past, so the message stayed queued for good. The entry now drops the released child and its event sink, and follows the record's fence, as a failure before indexing already did. * fix(native-chat): a withdrawn message shows no error, and a rejection outlasts the send's answer The error strip for a message the host accepted and then did not deliver matched the entry before the outbox reconciled, so a Stop's withdrawal, which the reconcile drops, showed "Orca could not send your message" with nothing to retry. It now reads the reconciled entry. A rejection the journal records before the send's own pending answer lands is final as well: that answer no longer puts the entry back to dispatching with no Retry. * fix(orchestration): a structured worker whose agent outlasts the preamble wait is left unknown, not torn down The preamble waits for its submission to be delivered while the worker's agent starts. When that wait ran out it threw operation_unknown, and the failed-start teardown then closed the session, which rejected the very preamble the host was about to deliver. It now reports a turn start nobody observed yet: the worker is start-unknown with its session kept, the host delivers the preamble when the agent starts, and the worker's report settles the dispatch as for any unobserved start. The receipt no longer suggests reading a screen a structured worker lacks. * fix(native-chat): a message rejected while its chat was closed reads as not sent A remount reads an entry it left dispatching as unconfirmed. When the journal had rejected it meanwhile, as a failed start or a quit now does, the reconcile left it unconfirmed: it blocked every later message behind a Retry and no reason, and the delivery probe, seeing the journal already answered, never ran. The reconcile now settles it as rejected like a dispatching one. * test(orchestration): name why the readiness settlement fakes are cast * fix(native-chat): keep each pane's own fence on frames so a failed restart is not resent * docs(native-chat): drop the fence from the admission the send effects run behind * docs(native-chat): give the fence move on release the reason that still holds * docs(native-chat): stop citing a write fence check in launch and mailbox comments Three places still gave the removed fence check as a reason: the launch replay said admission puts the ledger ahead of the fence, the launch surface said a send must name the lease it was admitted against, and the direct-mailbox path said the lease fence decides whether delivery is safe. Admission now checks only the writer lease. * refactor(native-chat): the provider child is its own record A conversation now outlives any number of provider children, so the child is one record on the conversation's entry instead of five loose fields beside its journal. It is written in one place: indexed only once an attach has fully succeeded, and ended through one function that an exit, a failed re-attach, a Stop and an eviction all share, matched on the child's generation and fence. - A failed attach writes no child, so there is nothing to unwind: the field unwind and the fence patch after it are gone. - Conversation writes read the record's fence, the way mutation admission already does; a child's own writes use its fence. The four stored-fence patches, and the settlement retry's overwrite of the conversation's fence, are gone. - The owed wind-down is its own tombstone, carrying the child it is owed for, and is no longer dropped when an attach replaced the whole entry. - Stop on a child still proving its start stops only the child: its lease goes back and the chat is told it is idle, but the journal, the holders and the readers stay. Close is that stop plus the conversation's close. - The settlement retry uses the conversation's own journal, opened through the host's one open. * fix(native-chat): the delivery loop alone settles a message its start or child failed A queued message was settled by whichever path happened to end the child first: the loop, the unexpected exit, eviction's work settlement, the open's leftover rule, and the startup branch that rejected every pending row. That gave two failure rows with different tones for one start, a loop that could hand over to a different child than the one it waited on, and a Claude start that died while starting reading unlike every other failed start. - The loop remembers the child it waited on. At handover, if that child is gone or replaced, it reads how it ended: a Stop continues; anything else writes one failure row and rejects every queued message with the same words, then stops. A child still starting whose start the adapter says did not land fails the same way. The exit, eviction and the settlement retry only settle the handed-over and legacy rows of the child that ended. - One failure row, always an error, keyed by the start. A start a view began that dies with nothing queued writes the same row through the same builder, so a second report revises it. - The open no longer rejects leftovers; the loop's first step does, and the open wakes it. - `awaitStarted` answers why a start did not land, so the row says it even when the loop sees the failure before the exit is processed. - Quit closes every conversation the way closing a chat does: what is still queued is rejected as closed, with or without a child, and a start the loop already has in flight is waited for so the child it produces is stopped rather than left behind. * refactor(native-chat): a stopped child ends on the one reading of its stop The eviction step reads a stop's result through `stopAgentSessionProviderRoot` and hands that verdict to the child's ending, so the host never forms a second view of whether the root is gone. Every ending carries it: a stop's comes from that reading, an exit's root is gone by definition, and a failed re-attach passes what its release saw. The end-of-child record can therefore also carry a stop whose root was not seen to go, which nothing ends on yet. * feat(native-chat): the host says it accepts a send before any agent has it The host now lists agent-session.accepted-send.v1 among its own runtime capabilities, the same string capable clients already send. A client can then tell a host that answers a send at acceptance, and admits a Stop with no writer before a turn starts, from an older one that still restarts the agent inside the send. Additive: an older client ignores a capability it does not know. * refactor(native-chat): an attach never opens a journal of its own The attach adopts the conversation's open journal, which outlives it, so it no longer opens one for a direct caller either. That leaves nothing for a failed adopted import to close, and the flag that told the two cases apart is gone. Tests that attach without a host open the conversation the way a host does. * fix(native-chat): a moved fence resends nothing on a host that accepts first The outbox treated any fence change as a new owner: it dropped the answer of a send in flight, queued that send to go out again under the same id, and unblocked a refused head. On an older host that is how a send the restart refused, unrecorded, gets another try. On a host that records every send before it starts an agent, a fence moves because that start ran, so the same rule resent into every failed start. With a fence stamped on every frame, that became a loop. The outbox now reacts to a fence change only when the host has not advertised that it accepts a send before any agent has it. On such a host, only a Retry or a new send goes out, and a failed start reaches the client as a rejected message it keeps with its Retry. Against an older host, or before one has answered, the outbox behaves as it did. Desktop and paired web share this hook. * refactor(native-chat): a child's end says whether the user or the host stopped it The end-of-child record's cause now tells a user's Stop from the host stopping the child for a cause of its own: `user-stop` and `host-stop` replace `stop`. The delivery loop goes on after a user's Stop, as before, and fails the start it was waiting on after a host stop, with the one error row and every queued message rejected, in the stop's reason when it gave one. The reason stays description only. Stop passes `user-stop`; nothing passes `host-stop` yet. * fix(native-chat): a chat whose only work is a queued message is not offered for resume A message accepted while the agent was starting counts as working in the chat, and quit rejects it as never sent. The teardown snapshot read the same working rule, so a relaunch offered to resume a chat whose agent never had the message. The snapshot now reads only what was handed over. * fix(native-chat): the conversation outlives its agent Opening a chat no longer starts its agent. A conversation is reached through one host accessor that opens its journal at rest, and a send is what starts the agent, through the delivery loop. One idle sweep, every five minutes, stops an agent that has been quiet for thirty minutes and owes no work, then drops an open journal handle that is only a cache. Its record, tab, status row and readers stay. - hold and release are no-ops; hold still builds the host for shipped mobile builds. - The holders, the holds, the release clock and the exit respawn are deleted. - Options, the model list, the goal and the context meter answer at rest; a model pick at rest is recorded as intent for the next start. - Compact, rewind, clear and goal changes start the agent first. A send does too when a rewind is still in doubt after the conversation opens. - Orchestration routes mail and group addresses on ownership (the record plus the chat tab), not on whether the process runs. An open dispatch keeps its worker running. - The restart continuation is a send; Resume all holds each slot until the message is handed over or rejected. - A read error never replaces a loaded transcript, and shows the host's own words. * test(native-chat): type the queued-message fixtures in the resume-offer tests * fix(native-chat): a start that dies while a message waits on it is that message's failed start Opening a chat's tab starts an agent for the view, and a send accepted meanwhile waits on it. When that start died, its exit wrote the start's error row and left the message queued, so the delivery loop started a second agent into the same failure and wrote a second row. A child's end now records where the conversation's journal stood, and the loop settles a message accepted before a failed start ended with that start: one row, under its key, and no second start. A message sent after the failure still gets a fresh start. * docs(native-chat): say what an attach's open conversation and unconfirmed ids are now * test(native-chat): pin what a failed start settles, and what a resume offer names A view's child that dies while a sent message waits settles that message only when it died starting and no child has taken its place: a proven child's crash, or a second start since, gets the message delivered. The resume offer names the handed-over message, never a newer one still queued. * test(native-chat): the failed-start pins fail on what the message became, not on a timeout * fix(native-chat): a restart offer ends when the chat's agent starts again The offer used to end only when the chat's newest user message changed, because opening a chat started its agent and that start could not be told apart from real activity. Opening a chat starts nothing now, so the host reads the fact it already publishes: a chat's status row goes from not host-owned to host-owned exactly when its agent is started. At that edge the offer and any failure record for the chat are withdrawn, unless the start is a resume action's own (its continuation is the oldest undelivered message). A continuation and a message racing to be first are decided at acceptance: the continuation is refused, quietly and with nothing filed, when any other message was accepted since the restart. A failed continuation start leaves the offer retryable, and each resume action sends its own message id. Deleted: the newest-user-message comparison, its journal reader, the continuation filter, and the failure ledger's own "answered by the chat" check. The marker still carries its message id for one release, so the previous build can read it. * fix(runtime): end a transcript stream when its client unsubscribes Desktop: the IPC subscription controller was dropped as soon as the streaming handler returned, which for most streams is right after it binds. A later runtime:unsubscribe then found nothing to abort, so the host kept the subscriber and derived and sent every publish to a channel no one listened to. The controller now lives until the renderer unsubscribes, resubscribes the same id, or goes away. Mobile: disposing an agentSession.subscribe stream now sends agentSession.unsubscribe with the stream's frame id, so the host ends that subscriber and leaves a sibling stream on the same socket running. The direct path now passes the frame id the relay path already passed. * test(orchestration): the preamble's host stub is typed, not cast The preamble send now takes only what it reads of the host, the send, the settlement wait and the record's fence, so its test builds that host with real types instead of `as never`. * fix(native-chat): one fact ends a restart offer: the chat moved on since the restart The offer is live while no other message has been accepted in the chat since the restart and its agent has not proved a start since. The offer list, the resume's reservation check and the continuation's acceptance check all read that one fact, so a message whose start then failed withdraws the offer too, and a stale click finds nothing to act on. The fact is read off the conversation's open handle, which the restart closed, so it is retired durably whenever it may have changed: a message accepted, a start proven. A close and reopen within the same run therefore cannot bring the offer back. A continuation rejected before it reached the agent does not count, so a retry after a failed start still runs. Deleted: the quit-time gate on withdrawal, which changed nothing because the withdrawal and the quit's own offer write share one queue; the per-action "withdrawn" flag and the separate acceptance check it paired with. * test(native-chat): an older build reads the restart offer this build records The offer lives in a file the previous release reads after a downgrade. Pin that against the pinned release's own capsule, and run the lane when the marker or the capsule changes. * fix(native-chat): read a restart offer against where the journal stood when it was taken "Since the restart" was read off the conversation's open handle, which the idle sweep closes: after a reopen, a message the user had already sent looked older than the handle and the withdrawn offer came back. The offer now records the journal position (epoch and sequence) at the moment it is taken, and a message accepted after that position, or a journal on another epoch, means the chat moved on. That is derived from the journal, so it holds across any number of closes and reopens. An older build's offer has no position; only a start withdraws it. Because the message half is now durable, the offer is no longer rewritten in the recovery file on every accepted message; a proven start still writes it, since only the host that saw the start knows of it. * test(native-chat): wait for the listing's retire write before reading the recovery file * fix(native-chat): keep the terminal-backed chat's read error over its local echoes Messages winning over a read error is right for the structured chat, whose read retries and whose messages came from the transcript. The terminal-backed view assembles its list from local echoes too (a launch prompt, a pending send), so a failed read there showed only those bubbles and no error. Only the structured pane now keeps messages over an error. * fix(native-chat): a start retries the exit settlement a failed journal write left owed An agent exit whose journal settlement write failed releases the lease latched until a retry lands. Reopening the chat used to be that retry; with reveal now only opening the journal, nothing retried it before the next app launch, and every send was refused. The start the send needs now runs the retry first, where the attach would. * perf(native-chat): answer the owner check without opening the chat Worktree activation calls agentSession.handoffStatus for every chat tab in the worktree, and the answer comes from the session record alone. Reaching it through the accessor opened each resting chat's journal (a full read, the crash-boundary write and a restored status publish), then kept it open for the idle window. It now checks the record and the adapter's support, as before this series, and opens nothing. * fix(native-chat): a read waiting on the session lock opens nothing once quit began The accessor checked for quit before queueing the open, so a read queued behind a session task ran its open after teardown had begun and indexed a journal no teardown step would close. The check now runs at the open itself. * fix(native-chat): read a failed resume's chat before calling it retryable Whether a failed resume is retryable is the offer's own rule: the chat has not moved on since the restart, read from its journal. The failure list read it only for a chat already open, so once the idle sweep closed a chat the user had moved on in, its failure showed Retry again, and the click did nothing. The list now opens the failed chats first, as the offer list does. * test(native-chat): type the provider event sink the settlement test reaches for * fix(native-chat): say the structured read keeps trying only where it does The structured pane's "Orca keeps trying to load it" line never showed: the view state filled in an untranslated fallback whenever the read error had no text, and the empty state prefers any message. The view state now leaves the message out, so the structured pane shows that line and the terminal-backed pane its own translated one. Mobile's structured lane does not resubscribe after an error frame, so it no longer makes the claim. * test(native-chat): await the send's settlement instead of polling for the start The at-rest send tests polled for the provider start with vi.waitFor's one-second default, which a loaded machine outran. They now await the host's own settlement of the message. * fix(native-chat): a restart offer resumes any time after the quit, and knows its own continuations The continuation's message id was dated by the quit, and the ledger refuses a new id dated more than a day back, so Resume or Retry a day after quitting was always refused (on main too). It is now dated by the resume action. Telling a rejected continuation from the user's own message read the operation ledger, whose rows expire after about a day; after that a failed resume stopped being retryable. The offer now records the continuation each action sends on its own capsule entry, bounded to the newest 16, so the ids end with the offer. The ledger read is deleted. * fix(orchestration): route no mail to a structured worker its orchestration released A structured worker is routed on ownership, and a resting worker's lease is released, so ownership held while its chat tab stayed listed. A worker the coordinator abandoned and then released, found at rest by the release, therefore still took peer mail and @worktree: broadcasts, and each one restarted its agent. Routing now also reads the orchestration's own resource row: once it is released, direct mail, group addressing and worker-show's addressable answer drop the worker, as they would a terminal worker whose terminal closed. The chat tab stays, and nothing new is stored. * fix(native-chat): a failed retry names the user's prompt, not Orca's continuation A resume's continuation is written to the chat before its start, so after a failed attempt the chat's newest user message is that rejected continuation. A second failure then showed Orca's own restart text as the chat's prompt. A retry now keeps the prompt its first failure named. * fix(orchestration): read the released row optionally, as the authority does worker-show's observation called the row lookup directly, which a runtime double without it threw on and failed the structured tab-retirement release. * fix(native-chat): the status bar drops a restart offer the chat moved on from The renderer re-read the host's restart offer only when a failed chat showed activity, so after a message withdrew a pending offer the host answered no chats while the status bar kept counting one, and clicking it opened nothing. The same watch now covers pending offers: a status change in an offered chat asks the host again, once. * test(native-chat): a roster of idle or finished children does not keep an agent awake The sweep reads owed background work through the shared child-work liveness that upstream's release clock adopted; a child that went idle or finished is not work the agent still owes. * fix(orchestration): a task dispatched into a resting structured worker keeps it running The sweep's open-dispatch check read only the worker-start dispatch that owns the worker's terminal resource, so a task later dispatched to the same worker (orchestration dispatch --to, which writes a dispatch with no worker row) did not count: after thirty quiet minutes the worker was stopped while that task was open, and its coordinator read exited. Any unsettled dispatch addressed to the worker's process incarnation now counts, derived from the existing rows. * docs(native-chat): comments stop describing the hold this PR removed Eight comments still justified orderings and teardown choices by a viewer or dispatch hold that pinned the provider child. Nothing holds any more; the orderings stand for the binding's redrive subscription and parked mail, and a chat's agent runs from a send until the idle sweep rests it. Comment-only. * fix(native-chat): a restart offer keeps the start its own continuation made Whose start ended an offer was decided at read time, from whether the offer's continuation was still the queued message. Once the provider refused that continuation, the child it had started read as someone else's start, so the offer ended and its failure showed no Retry. The delivery loop now records which queued message a start is for on the in-memory child, and the child's end carries it; the offer counts a start as its own when that message is one of its continuations. * fix(native-chat): an agent gets a full idle window after its owed work ends The sweep measured quiet only from the last journal row, so once a subagent, command, monitor or dispatch that had outlived the window ended, the agent was stopped at the next tick. A child can read done before the lead's wake-up turn writes anything, and stopping in that gap loses the wake-up. The sweep now counts owed work it observes as activity, which gives the agent the full window afterwards, as the release clock it replaced did. * test(claude): the options-read fixture runs a live child The fixture marked its conversation running with a hasProviderChild field the session type does not have, so the read took the at-rest path and refused a session with no record. It now carries a child, which is what the read checks. * test(native-chat): host tests reach its collaborators through a typed seam The rest-test rig and three test files read the host's private members with Reflect.get and cast the result. The host now exposes one test-only accessor, collaboratorsForTests(), and the subscribers class a subscriberCountForTests() beside its existing retainedActivityCountForTests(), so the tests are checked against the real types and the casts are gone. * refactor(orchestration): one owner answers a structured worker's custody Routing, group addressing, worker-show and the idle sweep each composed their own reading of whether orchestration still holds a structured worker, so each new obligation or retirement state had to be added to every reader. structured-worker-custody now derives both answers from the worker-terminal list state coordinators see in worker-list: addressable is owned and not released, and owed work is an active custody or an unsettled task dispatched to the same incarnation. The owner's state is read through the remote dispatch attachment too, as the terminal transfer lookup already does. Behaviour is unchanged; a settled worker awaiting its coordinator still rests. * refactor(orchestration): owed work is an open dispatch on the worker's incarnation A supervised worker's own dispatch context stays open exactly while the worker is active, so the separate active-custody branch only repeated it. Owed work is now one fact, which also states the policy that a worker awaiting its coordinator's decision may rest, and both custody decisions are written once at the top of the module. * fix(native-chat): a restart offer knows its continuations by a tag in their id The offer recorded each continuation id in a list on its capsule entry, capped at 16, and a running action's id in memory. Both could disagree with the journal: past the cap an old rejected continuation read as the chat moving on, and a crash during a retry restored the failure's older entry, which lacked the retry's id. Each continuation id now carries a tag derived from the offer (its teardown and chat), then the action's own part, so any continuation of this offer, queued or rejected, is recognised from the journal row and the marker alone. The persisted list, its cap and the in-memory action map are deleted; the agent-start withdrawal keeps an offer whose own continuation the start was for, read against the stored marker. * test(runtime): the legacy-worker reveal test judges its stale snapshot inside the wait The tui-idle probe reads through readTerminal, which now awaits the structured worker check before the PTY read, so the probe's snapshot request starts a microtask later. vi.waitFor missed it on its first check and polled again at 50 ms, the same moment the wait's own 50 ms timeout fired. The stale snapshot then resolved after the wait had already timed out, so the test passed without judging it, and the rejection landed before any handler was attached. Vitest reported that as an unhandled error and failed the shard. Polling every 1 ms sees the request within a few ms, so the snapshot is judged while the wait is still pending. * fix(native-chat): a view never restarts a chat whose last start failed A Claude chat whose CLI exits during startup left one red row per start, and every time a view bound to it (the chat opening right after its create died, or the user switching back to it) the hold started the CLI again, so the same launch-failure row repeated. Only a send retries a failed start now, the same rule provider-exit recovery already applied; the rule lives in one predicate the hold, exit recovery and the delivery loop share. * fix(native-chat): the idle sweep reads owed work every tick Owed work counted as activity, but the sweep read it only once the idle window had elapsed, so it refreshed the clock at most once a window. Work that ended just before the next read left the agent to be stopped at that read, moments after the work ended, which is the gap the refresh was meant to cover. The sweep now reads owed work on every tick for a started agent, so the window always runs from the last tick that saw work owed. * fix(native-chat): a continuation handed to the agent stays sent The offer read its own continuation as not reaching the agent while its dispatch was pending, which also covered one already handed over and still unanswered. When the wait for that answer ended first, the failure it filed read as retryable, and a retry sent a second continuation to an agent that may have acted on the first. Only a continuation still queued, or rejected, is now read as unsent. * test(native-chat): start the child the loop waits on with an attach, not a second view A view no longer starts a child whose last start failed, so the R2 case that waits on a child started since the failure now gets that child from a client attach, the one non-send starter left. * fix(native-chat): settle a gone generation's turn wherever a conversation opens A send that opens a chat this process had not read yet (after a crash, from a phone or the CLI) went through the delivery open, which never settled what the dead generation left running; only the read restore and a successful acquire did. When the send's start then failed, the turn stayed running for every reader. The settlement now runs in the one journal open, at the crash boundary, for every opener except an acquisition, which settles from the evidence it read before its reserve; the read restore's separate step is gone. * test(native-chat): prove the next child's start settles the turn an earlier child left The R1 case lost its only settlement assertion when the latch it checked was deleted. It now seeds the running turn the earlier child left and asserts it ends at the exit's receipt, with the exit's row, before the message is handed to the new child. * test(native-chat): count a failed start's rows by row, not by text Comparing the set of texts passed when two different rows carried the same words, which is the duplicate the test exists to catch. * test(native-chat): give the failed-start and stale-turn waits a loaded runner's budget * test(native-chat): the interrupted create's own retry continues again The merge of main's lease-latch fix replaced that test's retry of the interrupted create, under its own operation id, with a fresh start whose result nothing read. That fresh start passes with the released-reservation continuation deleted, so the case the fix exists for went untested. The retry and its assertion are main's again. * docs(native-chat): three comments that still had views starting agents A start with nothing queued now comes from a command, goal change or rewind; an interrupted compaction left alone would refuse every send, so no agent would ever start to finish it; and a current host raises the unattached read refusal only once quit began, with the attach window belonging to an older host. * test(native-chat): pin the open's and the send's start and row counts, however the view binds Opening a fresh chat whose starts fail makes one start and one row, with two views bound before or after the create's child died; one send makes one more of each. * test(native-chat): a reader's open settles the turn a failed exit settlement left running An exit whose settlement write failed leaves its turn running in the open journal. PR 1's open now settles it, and this pins the two reads that reach it here: a reader reopening a chat the idle sweep closed, and a read that opens the chat before the restart restore reaches it. * test(native-chat): the view-start test's starting window outlasts two subscriptions on a loaded runner A subscription reads the conversation before it returns, so under load the two views took longer than the create child's 300 ms start, which then exited before the test checked that it had not. The child now takes a second to fail. * fix(native-chat): settle a gone generation's turn at every open but an acquisition's The journal open skipped the settlement whenever the lease read reserved or live, to leave an acquisition's own open to the acquisition. But a lease a crashed process left in recovery also reads live, until the next acquire resolves it. A send that opened such a chat, from a phone or the CLI after a crash on a host that could not prove the old owner gone, skipped the settlement; when its start then failed, the dead turn stayed running for every reader. The acquisition now says it is the opener, and every other open settles, whatever the lease still claims. * test(native-chat): hold the create's start open until the views bind The "view binds while the create is still starting" case gave the create a 300 ms head start and asserted the views bound before it died. On a loaded runner the holds took longer, the create's exit landed first, and the case failed its own precondition. The create's initialize now waits on a gate the test releases once the views are bound. * test(native-chat): a read that reaches a crashed chat before the startup reconcile settles its turn On desktop the chat on screen at relaunch reads before startup reconciles the leases, while the dead process's lease still reads live. The open settles the turn it left running anyway, and the restore that follows finds it settled. * refactor(native-chat): drop the composer's second error formatter After the merge with main, every chat write in the composer path reports its failure as a typed outcome worded by the refusal-notice table, so the send's catch sees only a local throw. The {code, message} formatter this branch added for it has no payload left to format, and its claim to be the one way a chat words a failure is no longer true. The composer send is main's again. * test(native-chat): pin the reason on a message rejected while its chat was closed The reopen test checked only that the message reads as not sent; it now also checks the Retry row carries the host's reason. * docs(native-chat): drop the removed dispatch hold from six comments A worker's session no longer takes a dispatch hold, and no release clock rests a chat by visibility; the agent-launch comments, the abandon test, the teardown test and the refusal census still said so. * test(native-chat): rest the owner-status chat through the idle sweep, not a hold The activation-gate test from #22808 put its chat at rest by holding and releasing it, and passed the release-clock grace. This branch deleted both, so the case threw before it reached its assertions. It now moves the host's clock past the idle window and lets the sweep stop the agent and close the conversation, then asserts the same owner answer and activation gate. * fix(native-chat): show the structured pane's retrying line when a read fails The read transport always hands the pane the host's words, so the error state's "Orca keeps trying to load it" line, which showed only when there were none, was never seen: the pane showed the host's text twice, as its subtitle and on the status line under it. The structured pane now always says its read keeps retrying, and the host's text stays on the status line. The terminal-backed chat is unchanged. * test(native-chat): wait for a send's background start before the refusal oracle removes its store An accepted send wakes the delivery loop, which starts the agent in the background. The oracle's teardown disposed the loop but did not wait for that start, so its lease write could create a temp file in the store directory while the directory was being removed, failing the test with ENOTEMPTY about one run in four. The teardown now drains tracked starts before it closes the journals. * fix(native-chat): a start a message waited on gets one failure row, the delivery loop's When a queued message's start failed, two writers could report it under the same row: the delivery loop, when the adapter settled the start without proving it, and the exit settlement, when the child's exit landed. The last one won, so the chat's row could name a different cause than the one the message was rejected with, or be written twice. The exit settlement now writes the start's row only when no message is queued and the loop has not already recorded that start. A start for a command, goal change or rewind, with nothing queued, still gets its row from the exit. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
85067494a1 |
fix(native-chat): a request that failed reads as failed (#22944)
* refactor(native-chat): remove the unused terminal handoff No client ever called agentSession.requestHandoff or mounted the handoff chrome. Delete the handoff coordinator, the terminal-owner runtime, the proof write path and the unmounted UI. Keep agentSession.handoffStatus, which released desktop clients read for worktree activation, and let records an older build left mid handoff reconcile through the ordinary restart and recovery paths. * fix(native-chat): never let the pre-stop snapshot hold a chat's stop Eviction now drains delivered events before quit's resume-offer snapshot. An unbounded wait there sits ahead of the provider stop, so a sink whose journal write stalls kept the child running until the step deadline aborted the eviction. The offer is advisory: bound the drain and stop the child regardless. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop helpers only the terminal handoff called `claudeAuthEnvCarriedForward`, `isPathWithinDirectory` and `queryWindowsProcessRowsFresh` lost their last caller with the handoff. The fresh-scan tests now go through `queryWindowsProcessDescendants({ fresh: true })`, the teardown path that still depends on that contract. Co-Authored-By: Claude <noreply@anthropic.com> * docs(native-chat): stop citing the removed handoff in lifecycle comments Six comments still named the handoff coordinator, a handoff suspend, or a terminal-owned session as live participants in the flows they describe. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stalled snapshot drain without a cast Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that a start dead before proving owes no settlement The removed restart handoff test pinned this branch; nothing else did. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): keep the owner-status read behind an in-flight attach The handoff removal dropped the per-session queue from `handoffStatus`, so a read landing mid-start reported the reservation (no owner) instead of the settled chat owner, and shipped desktop clients blocked worktree activation on it. The read is queued again, as it was before the removal. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(terminal): remove the agent-session PTY write gate The gate only refused a write when a PTY had been bound to a chat session, and the only code that ever bound one was the terminal handoff this branch removes. With it gone, every admit/readmit returned "admitted" unconditionally, so the checks on the renderer write path, the runtime controller backstop, terminal.send, agent prompts, preview input and orchestration pointers, the refusal fields on terminal.send and worker-start receipts, the plugin and CLI refusal copy, and the adopted-pane orchestration routing could no longer run. Ordinary writes take the same path in the same order as before. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop the transcript helpers only the handoff called appendLegacyTranscriptMessages fed the terminal transcript catch-up and proveClaudeTranscriptBranch backed the terminal owner's exit proof. Both lost their last caller with the handoff. Their tests now go through the live entry points instead: the roster bounds through the legacy import, the pinned-read and growth tests through the ancestry replay the history window uses, and the marker rules through the string proof in their own file rather than the session-file resolver's. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): stop calling a starting chat "mid-handoff" A send refused because the chat's owner is not settled showed "The session is mid-handoff (<stage>)." in the composer. With the handoff gone, the stages that reach it are a chat that is still starting, or one whose previous agent process has not yet been confirmed stopped. The message now says which of the two it is. The refusal code is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stand-in roster decoder without a cast Co-Authored-By: Claude <noreply@anthropic.com> * refactor(codex): name the pinned rollout lookup for what it does With the terminal handoff gone, the module named codex-tui-rollout-proof holds only the pinned rollout lookup that structured Codex launches use to resume a thread, so the name described code that no longer exists. Rename the module and its options type. Also drop a mobile allowlist assertion that pinned the removed agentSession.requestHandoff method, which no longer exists to allow. * refactor(native-chat): type the owner-status reply as the host sends it The handoffStatus reply type still listed the terminal handoff's fields and states (terminal placement, host label, proof retry, queued and waiting phases, the to-terminal direction). No host writes them any more and the only client reader parses the reply as unknown, so they described nothing. The reply on the wire is unchanged. * refactor(native-chat): normalize terminal-handoff lease values once at decode Nothing in this build writes a terminal owner (`runtimeKind: 'tui'`) or the handoff's `preparing` / `old-owner-stopped` stages, but the in-memory types still admitted them, so readers across the host kept branches for values no path produces and the compiler could not point at them. The store now validates the on-disk shape, which still accepts those values so an older record is not quarantined, and maps them once while parsing: - `preparing` and `old-owner-stopped` become `recovering` - a `tui` lease becomes `native`; when it records a process it also becomes `conflicted`, the claim every build probes but never stops. A plain native owner would be stopped by restart recovery, here and in older builds. Revisions are taken over the normalized state on both sides of every compare, and the mapped record reaches disk with the store's first transaction, the same way the tab-id backfill does. The in-memory types narrow to what this build writes, and the branches that existed only for the removed values go. Structured-worker identity keeps its verdict for a former terminal owner by refusing a conflicted claim rather than a non-native kind. * refactor(native-chat): stop threading the owner kind through a reservation A reservation only ever names a native owner now, so the request no longer carries a kind and the reserved lease records `native` directly. The attach params keep `runtimeKind`: agentSession.ensure and create accept it, and the operation fingerprint stored in the ledger covers it. * test(native-chat): pin the legacy-lease rewrite with a transaction that changes nothing else Hiding a tab also committed the visibility index, so the no-op transaction wrote the file even when its open-time revision was wrong. Committing the index first leaves the pending rewrite as the only reason to write. * fix(native-chat): name a chat write by its target, not the owner generation A write carried the fence of the last frame the pane read, and the host refused it unless that fence was still current. An idle release and the restart after it each move the fence, and the release publishes nothing, so a send after a release was refused "Expected runtime fence 1; the session is at 3", and a Stop queued behind a cold start was refused as stale. Every write already names what it acts on: a send its conversation, a cancel its turn, a prompt answer its item revision, a rewind its epoch; an option is last-writer-wins. So admission stops comparing the client's fence, and the rebase that papered over one restart (admitAtResumedFence, resumedFromFence) goes with it. The writer-lease check stays, and so does the attach's compare-and-swap. Frames now stamp the fence read when each frame is sent instead of a copy each subscriber kept, which went stale on the same release. * fix(native-chat): every journal append reaches the chats that are open A journal write and its delivery to open readers were two calls, and some writers made only the first. A failed start whose lease could not be handed back, a provider revision with no frame behind it, and eviction's settlement were all journaled without reaching an open chat. A journal handle now reports every durable change, and the host's session map binds that report to the session's readers when the handle is set. Writers no longer publish what they append; the per-writer publish calls are deleted. * test(native-chat): an epoch replacement reaches the open chat * test(native-chat): each row reaches an open chat once, and a live handle enters only through the map * test(native-chat): give the legacy-lease store test a tab id so the backfill cannot supply its rewrite The seeded record had no surface tab id, so the next open backfilled one and that rewrite alone made the no-op transaction write. The test passed with the legacy-lease rewrite signal removed. * test(worktree-activation): restore the OMP surfaced-agent resume test The handoff removal deleted it alongside the terminal-owner tests, but it covers the surfaced-PTY block that still guards resume, including an agent whose ownership is unknown. * perf(native-chat): a publish behind a delivered commit reads nothing Each commit now delivers itself, so the publish a provider frame still sends afterwards found every reader caught up but still read rows and rebuilt the timeline for each one. A caught-up reader now skips the read. * test(native-chat): state why the teardown test's fake journal is safe to cast * docs(native-chat): say mutation admission checks only the writer lease * docs(native-chat): drop the send rebase from comments that still described it * fix(native-chat): a message is accepted, then delivered A send to a chat with no running agent restarted the agent inside the send call, before the message was recorded, so the client waited for the whole start and a failed restart refused the message. Claude held prompts sent during startup, and those could settle as "unconfirmed". A send is now accepted inside the session's serialized queue: one ledger row and one submission row marked handoverRecorded, published, answered pending. A per-session delivery loop exists while a message is queued. It starts the agent through the same serialized attach a hold uses, waits outside the queue for a Claude child to prove its start, and hands the oldest queued message over as its own serialized step, writing dispatch{pending} before the adapter call. A start it needed and did not get writes one error-tone row and rejects every queued message with the same words; a start Stop cancelled writes none. Settlement follows from the rows. A queued message is provably unwritten, so a close, an eviction or an exit rejects it. A handed-over message stays in doubt. A queued row at or below the sequence a handle found when it opened was left by an earlier process and is rejected at open, with no latch. Stop withdraws queued messages with no writer lease and no fence. An attach failure keeps the conversation open, and the attach adopts its journal. Owed work counts the loop and queued rows. A compaction or rewind found prepared when a conversation opens was started under a child this process no longer has, so the open settles it rather than leaving it to refuse every send until a view attaches. The open cursor is scoped to its epoch, because sequences restart when an epoch is replaced. Deleted: restart-before-admission, recordFailedRestart, the fence rebase, Claude's startup gate, the attach's forget on failure and its own crash boundary. Clients without agent-session.accepted-send.v1 get their reply held until the handover; the desktop and paired desktop lists advertise it. * fix(native-chat): settle queued messages only for the child that ended A child that proved its start and then exited before its message was handed over left the message queued: the exit settlement returned early when nothing else was in flight. Delivery then started another child for it, and a child that died the same way started another, without end and without a row. A retried settlement for an earlier generation, run by the attach that delivery started, did the opposite: with that generation's turn unfinished it rejected the message queued for the child being attached. The settlement now takes the rejection for queued messages from its caller. The unexpected exit and the eviction pass one, and it applies even with no other work in flight; the retry for an earlier generation passes none. * fix(native-chat): an adoption that fails to import keeps the conversation open The attach now writes into the conversation's own open journal, but a failed transcript import still closed it as if it were the attach's provisional one. The conversation stayed indexed with a closed journal, so every later send answered "could not be recorded" and every attach failed again until the app restarted. The import now closes only a journal the attach opened for itself. * perf(native-chat): the recovering open reads the journal once Every conversation open now goes through the recovering open, including the read restore of every chat at startup, which used to replay its journal once. The recovering open replayed it twice: once to probe it and again inside the open. The probe is now handed to the open as its load. * fix(native-chat): an attach that fails after indexing its child leaves no child behind A failed attach now keeps the conversation open, but a failure after `onAttached` indexed the child (the rewind or compaction recovery, or the attach's own success record) left that entry claiming a child the failure path had already released. The next send found the phantom, skipped the start, and wrote at a fence the journal had moved past, so the message stayed queued for good. The entry now drops the released child and its event sink, and follows the record's fence, as a failure before indexing already did. * fix(native-chat): a withdrawn message shows no error, and a rejection outlasts the send's answer The error strip for a message the host accepted and then did not deliver matched the entry before the outbox reconciled, so a Stop's withdrawal, which the reconcile drops, showed "Orca could not send your message" with nothing to retry. It now reads the reconciled entry. A rejection the journal records before the send's own pending answer lands is final as well: that answer no longer puts the entry back to dispatching with no Retry. * fix(orchestration): a structured worker whose agent outlasts the preamble wait is left unknown, not torn down The preamble waits for its submission to be delivered while the worker's agent starts. When that wait ran out it threw operation_unknown, and the failed-start teardown then closed the session, which rejected the very preamble the host was about to deliver. It now reports a turn start nobody observed yet: the worker is start-unknown with its session kept, the host delivers the preamble when the agent starts, and the worker's report settles the dispatch as for any unobserved start. The receipt no longer suggests reading a screen a structured worker lacks. * fix(native-chat): a message rejected while its chat was closed reads as not sent A remount reads an entry it left dispatching as unconfirmed. When the journal had rejected it meanwhile, as a failed start or a quit now does, the reconcile left it unconfirmed: it blocked every later message behind a Retry and no reason, and the delivery probe, seeing the journal already answered, never ran. The reconcile now settles it as rejected like a dispatching one. * test(orchestration): name why the readiness settlement fakes are cast * fix(native-chat): keep each pane's own fence on frames so a failed restart is not resent * docs(native-chat): drop the fence from the admission the send effects run behind * docs(native-chat): give the fence move on release the reason that still holds * docs(native-chat): stop citing a write fence check in launch and mailbox comments Three places still gave the removed fence check as a reason: the launch replay said admission puts the ledger ahead of the fence, the launch surface said a send must name the lease it was admitted against, and the direct-mailbox path said the lease fence decides whether delivery is safe. Admission now checks only the writer lease. * refactor(native-chat): the provider child is its own record A conversation now outlives any number of provider children, so the child is one record on the conversation's entry instead of five loose fields beside its journal. It is written in one place: indexed only once an attach has fully succeeded, and ended through one function that an exit, a failed re-attach, a Stop and an eviction all share, matched on the child's generation and fence. - A failed attach writes no child, so there is nothing to unwind: the field unwind and the fence patch after it are gone. - Conversation writes read the record's fence, the way mutation admission already does; a child's own writes use its fence. The four stored-fence patches, and the settlement retry's overwrite of the conversation's fence, are gone. - The owed wind-down is its own tombstone, carrying the child it is owed for, and is no longer dropped when an attach replaced the whole entry. - Stop on a child still proving its start stops only the child: its lease goes back and the chat is told it is idle, but the journal, the holders and the readers stay. Close is that stop plus the conversation's close. - The settlement retry uses the conversation's own journal, opened through the host's one open. * fix(native-chat): the delivery loop alone settles a message its start or child failed A queued message was settled by whichever path happened to end the child first: the loop, the unexpected exit, eviction's work settlement, the open's leftover rule, and the startup branch that rejected every pending row. That gave two failure rows with different tones for one start, a loop that could hand over to a different child than the one it waited on, and a Claude start that died while starting reading unlike every other failed start. - The loop remembers the child it waited on. At handover, if that child is gone or replaced, it reads how it ended: a Stop continues; anything else writes one failure row and rejects every queued message with the same words, then stops. A child still starting whose start the adapter says did not land fails the same way. The exit, eviction and the settlement retry only settle the handed-over and legacy rows of the child that ended. - One failure row, always an error, keyed by the start. A start a view began that dies with nothing queued writes the same row through the same builder, so a second report revises it. - The open no longer rejects leftovers; the loop's first step does, and the open wakes it. - `awaitStarted` answers why a start did not land, so the row says it even when the loop sees the failure before the exit is processed. - Quit closes every conversation the way closing a chat does: what is still queued is rejected as closed, with or without a child, and a start the loop already has in flight is waited for so the child it produces is stopped rather than left behind. * refactor(native-chat): a stopped child ends on the one reading of its stop The eviction step reads a stop's result through `stopAgentSessionProviderRoot` and hands that verdict to the child's ending, so the host never forms a second view of whether the root is gone. Every ending carries it: a stop's comes from that reading, an exit's root is gone by definition, and a failed re-attach passes what its release saw. The end-of-child record can therefore also carry a stop whose root was not seen to go, which nothing ends on yet. * feat(native-chat): the host says it accepts a send before any agent has it The host now lists agent-session.accepted-send.v1 among its own runtime capabilities, the same string capable clients already send. A client can then tell a host that answers a send at acceptance, and admits a Stop with no writer before a turn starts, from an older one that still restarts the agent inside the send. Additive: an older client ignores a capability it does not know. * refactor(native-chat): an attach never opens a journal of its own The attach adopts the conversation's open journal, which outlives it, so it no longer opens one for a direct caller either. That leaves nothing for a failed adopted import to close, and the flag that told the two cases apart is gone. Tests that attach without a host open the conversation the way a host does. * fix(native-chat): a moved fence resends nothing on a host that accepts first The outbox treated any fence change as a new owner: it dropped the answer of a send in flight, queued that send to go out again under the same id, and unblocked a refused head. On an older host that is how a send the restart refused, unrecorded, gets another try. On a host that records every send before it starts an agent, a fence moves because that start ran, so the same rule resent into every failed start. With a fence stamped on every frame, that became a loop. The outbox now reacts to a fence change only when the host has not advertised that it accepts a send before any agent has it. On such a host, only a Retry or a new send goes out, and a failed start reaches the client as a rejected message it keeps with its Retry. Against an older host, or before one has answered, the outbox behaves as it did. Desktop and paired web share this hook. * refactor(native-chat): a child's end says whether the user or the host stopped it The end-of-child record's cause now tells a user's Stop from the host stopping the child for a cause of its own: `user-stop` and `host-stop` replace `stop`. The delivery loop goes on after a user's Stop, as before, and fails the start it was waiting on after a host stop, with the one error row and every queued message rejected, in the stop's reason when it gave one. The reason stays description only. Stop passes `user-stop`; nothing passes `host-stop` yet. * fix(native-chat): a chat whose only work is a queued message is not offered for resume A message accepted while the agent was starting counts as working in the chat, and quit rejects it as never sent. The teardown snapshot read the same working rule, so a relaunch offered to resume a chat whose agent never had the message. The snapshot now reads only what was handed over. * test(native-chat): type the queued-message fixtures in the resume-offer tests * fix(native-chat): a start that dies while a message waits on it is that message's failed start Opening a chat's tab starts an agent for the view, and a send accepted meanwhile waits on it. When that start died, its exit wrote the start's error row and left the message queued, so the delivery loop started a second agent into the same failure and wrote a second row. A child's end now records where the conversation's journal stood, and the loop settles a message accepted before a failed start ended with that start: one row, under its key, and no second start. A message sent after the failure still gets a fresh start. * fix(native-chat): a request that failed reads as failed A structured chat whose only message the agent's start refused read as a green finish, and a cancelled structured turn did too: the host published a verdict only for turn records, and structured rows carried no `interrupted`. The host projection now reads the session's latest request: its turn's outcome, or `failure` for a send the agent or its start refused. A send that was withdrawn, or left undelivered by a restart or a close, fails nobody and makes nothing listable. The ingest publishes `interrupted` as the hook lanes do, and every reader decodes the verdict through one accessor, so a failure reads Failed on the dot, the rollups, history and `worktree ps`, behaves like a cancellation in every clean-finish policy, and notifies as "failed". * docs(native-chat): say what an attach's open conversation and unconfirmed ids are now * test(native-chat): a verdict change republishes the mobile status projection * refactor(native-chat): the store's retention trigger keeps its flag compare A verdict change always moves the completion clock the same check already reads, so a second verdict compare there caught nothing new. * test(native-chat): a user message the provider journaled keeps its session listed * test(native-chat): pin what a failed start settles, and what a resume offer names A view's child that dies while a sent message waits settles that message only when it died starting and no child has taken its place: a proven child's crash, or a second start since, gets the message delivered. The resume offer names the handed-over message, never a newer one still queued. * test(native-chat): the failed-start pins fail on what the message became, not on a timeout * fix(native-chat): a late provider-session update keeps a failed recovery record failed A provider-session heartbeat that rewrites a completed recovery record kept its interrupted flag but dropped the outcome it was copied with, so a live failed checkpoint read as a clean finish until the next status write. * test(orchestration): the preamble's host stub is typed, not cast The preamble send now takes only what it reads of the host, the send, the settlement wait and the record's fence, so its test builds that host with real types instead of `as never`. * test(native-chat): the terminal-bell check asserts the renamed verdict field The bell notification test still checked for agentInterrupted, which no longer exists, so it could not catch a verdict leaking into a bell dispatch. * fix(native-chat): a failed turn ranks like a completion for attention Attention readers (completion time, Smart Sort, sticky retention, Cmd+J Recent) now demote only a turn the user stopped. A failure is news the user has not seen, so it keeps its completion time, ranks in the Done class, stays retained after its pane goes away, and a retained failure reads failed in the worktree rollup instead of done. Clean-finish policy (hibernation, pane ownership, the value moment) still treats a failure like a stop. The retention trigger compares verdicts again: success -> failure no longer moves the completion clock. * fix(native-chat): a failed main agent reads failed while its subagents still work The verdict is now read from the main agent's own state, not the folded row: a main agent that is done and failed has a verdict even while its subagents keep the row working. Without mainAgent (history, worktree ps, older hosts) the old combined-done rule stands. Display marks the verdict through agentVerdictDisplayMark: a failure outranks every combined state on the agent's dot, label, tab badge, dashboard and activity rows; a stop marks only a done row, so a successful or stopped main agent with live subagents still reads working. Subagent rows keep their own state. The worktree card, terminal tab and Cmd+J rollups share one pane fold and rank a pending question, then failed, then working, monitoring, interrupted and done. worktree ps publishes the main agent's outcome on a working row, and the mobile mirror reads it. The store's change check, the paired-client mirror's equality and its epoch now see a verdict change on a working row, which otherwise moves no state or clock and left the worktree card reading working. Clean-finish policy is unchanged: a working row is never hibernated and has no completion time. * docs(native-chat): the worktree ps outcome comment no longer claims old hosts send it The field is new: an old host sends no outcome at all, so a reader falls back to interrupted. The removed clause said old hosts send it on done rows, which never shipped. * docs(native-chat): the status-store listing rule names provider-journaled user messages * fix(native-chat): a refused send notifies failed through the completion feed The host's completion feed followed only the newest turn, so a send the agent or its start refused, which creates no turn, read Failed on its row but sent no notification. The feed now follows the session's latest request, read from the projection the status feed already makes for the commit: a turn keeps its id, a refused send is named by its journal item key. It announces only while the session is idle, as the row reports a verdict, so queued sends refused one commit at a time notify once, and a withdrawn send falls back to a request already announced. * fix(native-chat): every copy of a row carries the main agent's own status History entries, sleep records and `worktree ps` rows carried a flattened top-level `outcome`, copied under different gates and without the main agent's clock. They now carry `mainAgent` (state, outcome, stateStartedAt), the type the live row already persists and sends, and every copy site takes it with `interrupted` through one function, `agentVerdictFields`. - The accessor reads `mainAgent` then the legacy flag; the mobile mirror matches it line for line. - Sleep records admit `mainAgent` with `normalizeMainAgentStatusField`, so a malformed value drops the field, never the record. - Mobile dates a main agent that failed under live subagents by its own clock, as desktop does, and its row equality compares `mainAgent`. - The activity feed reads a history entry's own `mainAgent` instead of rebuilding one; the sync key and history equality compare it. * test(native-chat): pin the worktree ps verdict across host and phone versions Pairs the real v1.4.212 host and phone row reader with this build: an old phone reads a new host's rows by `interrupted`, a new phone reads an old host's rows (no `mainAgent`) the same way, and a new phone reads a failure under live subagents as Failed, dated by `mainAgent.stateStartedAt`. The release checkout now carries the phone's self-contained row reader, and the lane runs when the `worktree ps` row producers change. * test(mobile): name the parity table's row for its role * fix(native-chat): a request that settles while the user is asked something notifies once The completion edge waited for an idle session, and a pending prompt (including a subagent's approval) is not idle. Structured chat has no other attention producer, so a main turn that finished while a subagent waited on the user sent nothing until the prompt was answered. The edge now waits only on owed work (a running turn or an unanswered send), which the projection reports even beneath a pending prompt. A request that settles with a prompt pending announces once; the renderer words it "needs input" from the host status mirror's `attention`, and answering the prompt keeps the same request identity, so it does not announce again. The wire shape is unchanged. * fix(native-chat): the completion says when the user is being asked A request that settles while a prompt waits on the user was worded "needs input" from the renderer's status-feed mirror. Remote clients receive the status and completion streams over separate sockets, so they can arrive in either order and the wording could be wrong both ways. The host already knows at emit time, so the completion now carries an optional `awaitingUser: true` in that case and omits it otherwise. The renderer words the notification from that field alone and no longer reads the status mirror. Old clients ignore the field and word by outcome; old hosts never send it. * fix(worktree-status): a departed agent's failure yields to live work on the worktree card A retained failed agent has no expiry, so ranking it with a live failure pinned the card to Failed over other panes' live work. It now ranks below working, monitoring and permission, and above every finished outcome. * docs(agent-status): a departed agent's failure ranks below live work on the worktree card * fix(native-chat): a view never restarts a chat whose last start failed A Claude chat whose CLI exits during startup left one red row per start, and every time a view bound to it (the chat opening right after its create died, or the user switching back to it) the hold started the CLI again, so the same launch-failure row repeated. Only a send retries a failed start now, the same rule provider-exit recovery already applied; the rule lives in one predicate the hold, exit recovery and the delivery loop share. * test(native-chat): start the child the loop waits on with an attach, not a second view A view no longer starts a child whose last start failed, so the R2 case that waits on a child started since the failure now gets that child from a client attach, the one non-send starter left. * fix(native-chat): settle a gone generation's turn wherever a conversation opens A send that opens a chat this process had not read yet (after a crash, from a phone or the CLI) went through the delivery open, which never settled what the dead generation left running; only the read restore and a successful acquire did. When the send's start then failed, the turn stayed running for every reader. The settlement now runs in the one journal open, at the crash boundary, for every opener except an acquisition, which settles from the evidence it read before its reserve; the read restore's separate step is gone. * test(native-chat): prove the next child's start settles the turn an earlier child left The R1 case lost its only settlement assertion when the latch it checked was deleted. It now seeds the running turn the earlier child left and asserts it ends at the exit's receipt, with the exit's row, before the message is handed to the new child. * test(native-chat): count a failed start's rows by row, not by text Comparing the set of texts passed when two different rows carried the same words, which is the duplicate the test exists to catch. * test(cross-version): load the phone row readers without mobile's toolchain Vite transforms a file against its nearest tsconfig, and mobile/tsconfig.json extends expo/tsconfig.base.json, which the root-only cross-version lane never installs. The worktree ps verdict suite imported the current phone row reader from mobile/ directly, so CI failed with TSConfckParseError before any test ran. The harness now imports a copy of the working-tree reader placed under the checkout cache, where the root tsconfig applies, as it already does for the release checkout's copy. Both readers are still the real files. * test(cross-version): keep the checkout path-guard message and justify the copy import's cast * test(native-chat): give the failed-start and stale-turn waits a loaded runner's budget * test(native-chat): pin the open's and the send's start and row counts, however the view binds Opening a fresh chat whose starts fail makes one start and one row, with two views bound before or after the create's child died; one send makes one more of each. * fix(native-chat): settle a gone generation's turn at every open but an acquisition's The journal open skipped the settlement whenever the lease read reserved or live, to leave an acquisition's own open to the acquisition. But a lease a crashed process left in recovery also reads live, until the next acquire resolves it. A send that opened such a chat, from a phone or the CLI after a crash on a host that could not prove the old owner gone, skipped the settlement; when its start then failed, the dead turn stayed running for every reader. The acquisition now says it is the opener, and every other open settles, whatever the lease still claims. * test(native-chat): hold the create's start open until the views bind The "view binds while the create is still starting" case gave the create a 300 ms head start and asserted the views bound before it died. On a loaded runner the holds took longer, the create's exit landed first, and the case failed its own precondition. The create's initialize now waits on a gate the test releases once the views are bound. * refactor(native-chat): drop the composer's second error formatter After the merge with main, every chat write in the composer path reports its failure as a typed outcome worded by the refusal-notice table, so the send's catch sees only a local throw. The {code, message} formatter this branch added for it has no payload left to format, and its claim to be the one way a chat words a failure is no longer true. The composer send is main's again. * test(native-chat): pin the reason on a message rejected while its chat was closed The reopen test checked only that the message reads as not sent; it now also checks the Retry row carries the host's reason. * fix(native-chat): a send the provider never received after a restart has no verdict Restart reconciliation rejects a crash-stranded send that is absent from a trustworthy provider history with reason 'not_delivered'. Nobody failed that send, but the verdict allowlist did not name it, so after a crash the chat read Failed, was listed, and could notify "failed". Give the reason a shared constant (persisted value unchanged), add it to the no-verdict set, and treat it as an internal marker so the Retry row no longer shows the raw string. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
c8f9a65ff8 |
test(e2e): keep the Kitty-arming app alive in the Option-composed spec (#23495)
The host grounds Kitty flags a finished command left armed, so a bare printf arm flipped back to 0 and raced the flags poll. Arm with a live cat foreground and pass per-test flags into setup instead of re-arming. |
||
|
|
bfe476f922 |
fix(native-chat): a message is accepted, then delivered (#22821)
* refactor(native-chat): remove the unused terminal handoff No client ever called agentSession.requestHandoff or mounted the handoff chrome. Delete the handoff coordinator, the terminal-owner runtime, the proof write path and the unmounted UI. Keep agentSession.handoffStatus, which released desktop clients read for worktree activation, and let records an older build left mid handoff reconcile through the ordinary restart and recovery paths. * fix(native-chat): never let the pre-stop snapshot hold a chat's stop Eviction now drains delivered events before quit's resume-offer snapshot. An unbounded wait there sits ahead of the provider stop, so a sink whose journal write stalls kept the child running until the step deadline aborted the eviction. The offer is advisory: bound the drain and stop the child regardless. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop helpers only the terminal handoff called `claudeAuthEnvCarriedForward`, `isPathWithinDirectory` and `queryWindowsProcessRowsFresh` lost their last caller with the handoff. The fresh-scan tests now go through `queryWindowsProcessDescendants({ fresh: true })`, the teardown path that still depends on that contract. Co-Authored-By: Claude <noreply@anthropic.com> * docs(native-chat): stop citing the removed handoff in lifecycle comments Six comments still named the handoff coordinator, a handoff suspend, or a terminal-owned session as live participants in the flows they describe. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stalled snapshot drain without a cast Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that a start dead before proving owes no settlement The removed restart handoff test pinned this branch; nothing else did. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): keep the owner-status read behind an in-flight attach The handoff removal dropped the per-session queue from `handoffStatus`, so a read landing mid-start reported the reservation (no owner) instead of the settled chat owner, and shipped desktop clients blocked worktree activation on it. The read is queued again, as it was before the removal. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(terminal): remove the agent-session PTY write gate The gate only refused a write when a PTY had been bound to a chat session, and the only code that ever bound one was the terminal handoff this branch removes. With it gone, every admit/readmit returned "admitted" unconditionally, so the checks on the renderer write path, the runtime controller backstop, terminal.send, agent prompts, preview input and orchestration pointers, the refusal fields on terminal.send and worker-start receipts, the plugin and CLI refusal copy, and the adopted-pane orchestration routing could no longer run. Ordinary writes take the same path in the same order as before. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop the transcript helpers only the handoff called appendLegacyTranscriptMessages fed the terminal transcript catch-up and proveClaudeTranscriptBranch backed the terminal owner's exit proof. Both lost their last caller with the handoff. Their tests now go through the live entry points instead: the roster bounds through the legacy import, the pinned-read and growth tests through the ancestry replay the history window uses, and the marker rules through the string proof in their own file rather than the session-file resolver's. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): stop calling a starting chat "mid-handoff" A send refused because the chat's owner is not settled showed "The session is mid-handoff (<stage>)." in the composer. With the handoff gone, the stages that reach it are a chat that is still starting, or one whose previous agent process has not yet been confirmed stopped. The message now says which of the two it is. The refusal code is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stand-in roster decoder without a cast Co-Authored-By: Claude <noreply@anthropic.com> * refactor(codex): name the pinned rollout lookup for what it does With the terminal handoff gone, the module named codex-tui-rollout-proof holds only the pinned rollout lookup that structured Codex launches use to resume a thread, so the name described code that no longer exists. Rename the module and its options type. Also drop a mobile allowlist assertion that pinned the removed agentSession.requestHandoff method, which no longer exists to allow. * refactor(native-chat): type the owner-status reply as the host sends it The handoffStatus reply type still listed the terminal handoff's fields and states (terminal placement, host label, proof retry, queued and waiting phases, the to-terminal direction). No host writes them any more and the only client reader parses the reply as unknown, so they described nothing. The reply on the wire is unchanged. * refactor(native-chat): normalize terminal-handoff lease values once at decode Nothing in this build writes a terminal owner (`runtimeKind: 'tui'`) or the handoff's `preparing` / `old-owner-stopped` stages, but the in-memory types still admitted them, so readers across the host kept branches for values no path produces and the compiler could not point at them. The store now validates the on-disk shape, which still accepts those values so an older record is not quarantined, and maps them once while parsing: - `preparing` and `old-owner-stopped` become `recovering` - a `tui` lease becomes `native`; when it records a process it also becomes `conflicted`, the claim every build probes but never stops. A plain native owner would be stopped by restart recovery, here and in older builds. Revisions are taken over the normalized state on both sides of every compare, and the mapped record reaches disk with the store's first transaction, the same way the tab-id backfill does. The in-memory types narrow to what this build writes, and the branches that existed only for the removed values go. Structured-worker identity keeps its verdict for a former terminal owner by refusing a conflicted claim rather than a non-native kind. * refactor(native-chat): stop threading the owner kind through a reservation A reservation only ever names a native owner now, so the request no longer carries a kind and the reserved lease records `native` directly. The attach params keep `runtimeKind`: agentSession.ensure and create accept it, and the operation fingerprint stored in the ledger covers it. * test(native-chat): pin the legacy-lease rewrite with a transaction that changes nothing else Hiding a tab also committed the visibility index, so the no-op transaction wrote the file even when its open-time revision was wrong. Committing the index first leaves the pending rewrite as the only reason to write. * fix(native-chat): name a chat write by its target, not the owner generation A write carried the fence of the last frame the pane read, and the host refused it unless that fence was still current. An idle release and the restart after it each move the fence, and the release publishes nothing, so a send after a release was refused "Expected runtime fence 1; the session is at 3", and a Stop queued behind a cold start was refused as stale. Every write already names what it acts on: a send its conversation, a cancel its turn, a prompt answer its item revision, a rewind its epoch; an option is last-writer-wins. So admission stops comparing the client's fence, and the rebase that papered over one restart (admitAtResumedFence, resumedFromFence) goes with it. The writer-lease check stays, and so does the attach's compare-and-swap. Frames now stamp the fence read when each frame is sent instead of a copy each subscriber kept, which went stale on the same release. * fix(native-chat): every journal append reaches the chats that are open A journal write and its delivery to open readers were two calls, and some writers made only the first. A failed start whose lease could not be handed back, a provider revision with no frame behind it, and eviction's settlement were all journaled without reaching an open chat. A journal handle now reports every durable change, and the host's session map binds that report to the session's readers when the handle is set. Writers no longer publish what they append; the per-writer publish calls are deleted. * test(native-chat): an epoch replacement reaches the open chat * test(native-chat): each row reaches an open chat once, and a live handle enters only through the map * test(native-chat): give the legacy-lease store test a tab id so the backfill cannot supply its rewrite The seeded record had no surface tab id, so the next open backfilled one and that rewrite alone made the no-op transaction write. The test passed with the legacy-lease rewrite signal removed. * test(worktree-activation): restore the OMP surfaced-agent resume test The handoff removal deleted it alongside the terminal-owner tests, but it covers the surfaced-PTY block that still guards resume, including an agent whose ownership is unknown. * perf(native-chat): a publish behind a delivered commit reads nothing Each commit now delivers itself, so the publish a provider frame still sends afterwards found every reader caught up but still read rows and rebuilt the timeline for each one. A caught-up reader now skips the read. * test(native-chat): state why the teardown test's fake journal is safe to cast * docs(native-chat): say mutation admission checks only the writer lease * docs(native-chat): drop the send rebase from comments that still described it * fix(native-chat): a message is accepted, then delivered A send to a chat with no running agent restarted the agent inside the send call, before the message was recorded, so the client waited for the whole start and a failed restart refused the message. Claude held prompts sent during startup, and those could settle as "unconfirmed". A send is now accepted inside the session's serialized queue: one ledger row and one submission row marked handoverRecorded, published, answered pending. A per-session delivery loop exists while a message is queued. It starts the agent through the same serialized attach a hold uses, waits outside the queue for a Claude child to prove its start, and hands the oldest queued message over as its own serialized step, writing dispatch{pending} before the adapter call. A start it needed and did not get writes one error-tone row and rejects every queued message with the same words; a start Stop cancelled writes none. Settlement follows from the rows. A queued message is provably unwritten, so a close, an eviction or an exit rejects it. A handed-over message stays in doubt. A queued row at or below the sequence a handle found when it opened was left by an earlier process and is rejected at open, with no latch. Stop withdraws queued messages with no writer lease and no fence. An attach failure keeps the conversation open, and the attach adopts its journal. Owed work counts the loop and queued rows. A compaction or rewind found prepared when a conversation opens was started under a child this process no longer has, so the open settles it rather than leaving it to refuse every send until a view attaches. The open cursor is scoped to its epoch, because sequences restart when an epoch is replaced. Deleted: restart-before-admission, recordFailedRestart, the fence rebase, Claude's startup gate, the attach's forget on failure and its own crash boundary. Clients without agent-session.accepted-send.v1 get their reply held until the handover; the desktop and paired desktop lists advertise it. * fix(native-chat): settle queued messages only for the child that ended A child that proved its start and then exited before its message was handed over left the message queued: the exit settlement returned early when nothing else was in flight. Delivery then started another child for it, and a child that died the same way started another, without end and without a row. A retried settlement for an earlier generation, run by the attach that delivery started, did the opposite: with that generation's turn unfinished it rejected the message queued for the child being attached. The settlement now takes the rejection for queued messages from its caller. The unexpected exit and the eviction pass one, and it applies even with no other work in flight; the retry for an earlier generation passes none. * fix(native-chat): an adoption that fails to import keeps the conversation open The attach now writes into the conversation's own open journal, but a failed transcript import still closed it as if it were the attach's provisional one. The conversation stayed indexed with a closed journal, so every later send answered "could not be recorded" and every attach failed again until the app restarted. The import now closes only a journal the attach opened for itself. * perf(native-chat): the recovering open reads the journal once Every conversation open now goes through the recovering open, including the read restore of every chat at startup, which used to replay its journal once. The recovering open replayed it twice: once to probe it and again inside the open. The probe is now handed to the open as its load. * fix(native-chat): an attach that fails after indexing its child leaves no child behind A failed attach now keeps the conversation open, but a failure after `onAttached` indexed the child (the rewind or compaction recovery, or the attach's own success record) left that entry claiming a child the failure path had already released. The next send found the phantom, skipped the start, and wrote at a fence the journal had moved past, so the message stayed queued for good. The entry now drops the released child and its event sink, and follows the record's fence, as a failure before indexing already did. * fix(native-chat): a withdrawn message shows no error, and a rejection outlasts the send's answer The error strip for a message the host accepted and then did not deliver matched the entry before the outbox reconciled, so a Stop's withdrawal, which the reconcile drops, showed "Orca could not send your message" with nothing to retry. It now reads the reconciled entry. A rejection the journal records before the send's own pending answer lands is final as well: that answer no longer puts the entry back to dispatching with no Retry. * fix(orchestration): a structured worker whose agent outlasts the preamble wait is left unknown, not torn down The preamble waits for its submission to be delivered while the worker's agent starts. When that wait ran out it threw operation_unknown, and the failed-start teardown then closed the session, which rejected the very preamble the host was about to deliver. It now reports a turn start nobody observed yet: the worker is start-unknown with its session kept, the host delivers the preamble when the agent starts, and the worker's report settles the dispatch as for any unobserved start. The receipt no longer suggests reading a screen a structured worker lacks. * fix(native-chat): a message rejected while its chat was closed reads as not sent A remount reads an entry it left dispatching as unconfirmed. When the journal had rejected it meanwhile, as a failed start or a quit now does, the reconcile left it unconfirmed: it blocked every later message behind a Retry and no reason, and the delivery probe, seeing the journal already answered, never ran. The reconcile now settles it as rejected like a dispatching one. * test(orchestration): name why the readiness settlement fakes are cast * fix(native-chat): keep each pane's own fence on frames so a failed restart is not resent * docs(native-chat): drop the fence from the admission the send effects run behind * docs(native-chat): give the fence move on release the reason that still holds * docs(native-chat): stop citing a write fence check in launch and mailbox comments Three places still gave the removed fence check as a reason: the launch replay said admission puts the ledger ahead of the fence, the launch surface said a send must name the lease it was admitted against, and the direct-mailbox path said the lease fence decides whether delivery is safe. Admission now checks only the writer lease. * refactor(native-chat): the provider child is its own record A conversation now outlives any number of provider children, so the child is one record on the conversation's entry instead of five loose fields beside its journal. It is written in one place: indexed only once an attach has fully succeeded, and ended through one function that an exit, a failed re-attach, a Stop and an eviction all share, matched on the child's generation and fence. - A failed attach writes no child, so there is nothing to unwind: the field unwind and the fence patch after it are gone. - Conversation writes read the record's fence, the way mutation admission already does; a child's own writes use its fence. The four stored-fence patches, and the settlement retry's overwrite of the conversation's fence, are gone. - The owed wind-down is its own tombstone, carrying the child it is owed for, and is no longer dropped when an attach replaced the whole entry. - Stop on a child still proving its start stops only the child: its lease goes back and the chat is told it is idle, but the journal, the holders and the readers stay. Close is that stop plus the conversation's close. - The settlement retry uses the conversation's own journal, opened through the host's one open. * fix(native-chat): the delivery loop alone settles a message its start or child failed A queued message was settled by whichever path happened to end the child first: the loop, the unexpected exit, eviction's work settlement, the open's leftover rule, and the startup branch that rejected every pending row. That gave two failure rows with different tones for one start, a loop that could hand over to a different child than the one it waited on, and a Claude start that died while starting reading unlike every other failed start. - The loop remembers the child it waited on. At handover, if that child is gone or replaced, it reads how it ended: a Stop continues; anything else writes one failure row and rejects every queued message with the same words, then stops. A child still starting whose start the adapter says did not land fails the same way. The exit, eviction and the settlement retry only settle the handed-over and legacy rows of the child that ended. - One failure row, always an error, keyed by the start. A start a view began that dies with nothing queued writes the same row through the same builder, so a second report revises it. - The open no longer rejects leftovers; the loop's first step does, and the open wakes it. - `awaitStarted` answers why a start did not land, so the row says it even when the loop sees the failure before the exit is processed. - Quit closes every conversation the way closing a chat does: what is still queued is rejected as closed, with or without a child, and a start the loop already has in flight is waited for so the child it produces is stopped rather than left behind. * refactor(native-chat): a stopped child ends on the one reading of its stop The eviction step reads a stop's result through `stopAgentSessionProviderRoot` and hands that verdict to the child's ending, so the host never forms a second view of whether the root is gone. Every ending carries it: a stop's comes from that reading, an exit's root is gone by definition, and a failed re-attach passes what its release saw. The end-of-child record can therefore also carry a stop whose root was not seen to go, which nothing ends on yet. * feat(native-chat): the host says it accepts a send before any agent has it The host now lists agent-session.accepted-send.v1 among its own runtime capabilities, the same string capable clients already send. A client can then tell a host that answers a send at acceptance, and admits a Stop with no writer before a turn starts, from an older one that still restarts the agent inside the send. Additive: an older client ignores a capability it does not know. * refactor(native-chat): an attach never opens a journal of its own The attach adopts the conversation's open journal, which outlives it, so it no longer opens one for a direct caller either. That leaves nothing for a failed adopted import to close, and the flag that told the two cases apart is gone. Tests that attach without a host open the conversation the way a host does. * fix(native-chat): a moved fence resends nothing on a host that accepts first The outbox treated any fence change as a new owner: it dropped the answer of a send in flight, queued that send to go out again under the same id, and unblocked a refused head. On an older host that is how a send the restart refused, unrecorded, gets another try. On a host that records every send before it starts an agent, a fence moves because that start ran, so the same rule resent into every failed start. With a fence stamped on every frame, that became a loop. The outbox now reacts to a fence change only when the host has not advertised that it accepts a send before any agent has it. On such a host, only a Retry or a new send goes out, and a failed start reaches the client as a rejected message it keeps with its Retry. Against an older host, or before one has answered, the outbox behaves as it did. Desktop and paired web share this hook. * refactor(native-chat): a child's end says whether the user or the host stopped it The end-of-child record's cause now tells a user's Stop from the host stopping the child for a cause of its own: `user-stop` and `host-stop` replace `stop`. The delivery loop goes on after a user's Stop, as before, and fails the start it was waiting on after a host stop, with the one error row and every queued message rejected, in the stop's reason when it gave one. The reason stays description only. Stop passes `user-stop`; nothing passes `host-stop` yet. * fix(native-chat): a chat whose only work is a queued message is not offered for resume A message accepted while the agent was starting counts as working in the chat, and quit rejects it as never sent. The teardown snapshot read the same working rule, so a relaunch offered to resume a chat whose agent never had the message. The snapshot now reads only what was handed over. * test(native-chat): type the queued-message fixtures in the resume-offer tests * fix(native-chat): a start that dies while a message waits on it is that message's failed start Opening a chat's tab starts an agent for the view, and a send accepted meanwhile waits on it. When that start died, its exit wrote the start's error row and left the message queued, so the delivery loop started a second agent into the same failure and wrote a second row. A child's end now records where the conversation's journal stood, and the loop settles a message accepted before a failed start ended with that start: one row, under its key, and no second start. A message sent after the failure still gets a fresh start. * docs(native-chat): say what an attach's open conversation and unconfirmed ids are now * test(native-chat): pin what a failed start settles, and what a resume offer names A view's child that dies while a sent message waits settles that message only when it died starting and no child has taken its place: a proven child's crash, or a second start since, gets the message delivered. The resume offer names the handed-over message, never a newer one still queued. * test(native-chat): the failed-start pins fail on what the message became, not on a timeout * test(orchestration): the preamble's host stub is typed, not cast The preamble send now takes only what it reads of the host, the send, the settlement wait and the record's fence, so its test builds that host with real types instead of `as never`. * fix(native-chat): a view never restarts a chat whose last start failed A Claude chat whose CLI exits during startup left one red row per start, and every time a view bound to it (the chat opening right after its create died, or the user switching back to it) the hold started the CLI again, so the same launch-failure row repeated. Only a send retries a failed start now, the same rule provider-exit recovery already applied; the rule lives in one predicate the hold, exit recovery and the delivery loop share. * test(native-chat): start the child the loop waits on with an attach, not a second view A view no longer starts a child whose last start failed, so the R2 case that waits on a child started since the failure now gets that child from a client attach, the one non-send starter left. * fix(native-chat): settle a gone generation's turn wherever a conversation opens A send that opens a chat this process had not read yet (after a crash, from a phone or the CLI) went through the delivery open, which never settled what the dead generation left running; only the read restore and a successful acquire did. When the send's start then failed, the turn stayed running for every reader. The settlement now runs in the one journal open, at the crash boundary, for every opener except an acquisition, which settles from the evidence it read before its reserve; the read restore's separate step is gone. * test(native-chat): prove the next child's start settles the turn an earlier child left The R1 case lost its only settlement assertion when the latch it checked was deleted. It now seeds the running turn the earlier child left and asserts it ends at the exit's receipt, with the exit's row, before the message is handed to the new child. * test(native-chat): count a failed start's rows by row, not by text Comparing the set of texts passed when two different rows carried the same words, which is the duplicate the test exists to catch. * test(native-chat): give the failed-start and stale-turn waits a loaded runner's budget * test(native-chat): pin the open's and the send's start and row counts, however the view binds Opening a fresh chat whose starts fail makes one start and one row, with two views bound before or after the create's child died; one send makes one more of each. * fix(native-chat): settle a gone generation's turn at every open but an acquisition's The journal open skipped the settlement whenever the lease read reserved or live, to leave an acquisition's own open to the acquisition. But a lease a crashed process left in recovery also reads live, until the next acquire resolves it. A send that opened such a chat, from a phone or the CLI after a crash on a host that could not prove the old owner gone, skipped the settlement; when its start then failed, the dead turn stayed running for every reader. The acquisition now says it is the opener, and every other open settles, whatever the lease still claims. * test(native-chat): hold the create's start open until the views bind The "view binds while the create is still starting" case gave the create a 300 ms head start and asserted the views bound before it died. On a loaded runner the holds took longer, the create's exit landed first, and the case failed its own precondition. The create's initialize now waits on a gate the test releases once the views are bound. * refactor(native-chat): drop the composer's second error formatter After the merge with main, every chat write in the composer path reports its failure as a typed outcome worded by the refusal-notice table, so the send's catch sees only a local throw. The {code, message} formatter this branch added for it has no payload left to format, and its claim to be the one way a chat words a failure is no longer true. The composer send is main's again. * test(native-chat): pin the reason on a message rejected while its chat was closed The reopen test checked only that the message reads as not sent; it now also checks the Retry row carries the host's reason. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
6c56c0a3dc |
fix(browser): a failed SSH route keeps its card while the host redials (#23465)
* fix(browser): a failed SSH route keeps its card while the host redials A browser route that already failed swapped its "SSH connection unavailable" card for "Connecting" on every dial of its host, and re-ran prepare once per dial cycle, so the card flickered and its buttons detached mid-click. Only a route that is still preparing now waits on a dialing host; a failed route keeps its card until the host actually connects, which re-derives it. Retry and Try anyway land on preparing together with the new attempt, so a press while the host dials waits for the connect instead of starting a prepare the effect immediately cancels. The escape-hatch e2e now makes the host truly unreachable before the disconnect; it passed before only because the card stayed latched over a host the terminal had already reconnected. * fix(browser): an unrouted SSH route waits for a dialing host too Only a failed or ready route is exempt from the host wait; a route that just became routed (or still shows another target's page) has no answer for this target, so it must not start a prepare that its own preparing write cancels. The escape-hatch spec now reads the settled failure from the renderer store: main's ssh:getState drops the entry on disconnect and on a failed connect, so its status is null there and never matches the failure pattern. |
||
|
|
29847641ab |
test(e2e): fix failures and improve stability (#23480)
- Narrow toolbar width and set window minimums for consistent testing - Add node_modules symlink to fixture for ESM import resolution - Exercise manual paging and fix button selector - Preserve repo filters in reveal workflow - Adjust timing strategy and increase test timeout |
||
|
|
10807d8b45 |
test(e2e): stabilize terminal shortcut Kitty and Ctrl+C tests (#23472)
Shift+Enter: the host now grounds keyboard modes a finished command left armed, so the test's one-shot `printf '\033[>1u'` was reset to 0 before the poll. Keep the command alive via `read` while asserting, then let it pop its own flags on Enter. Ctrl+C: each test opens a fresh tab, and SIGINT during shell startup can kill the shell and close the tab. Wait for a ready prompt before interrupting. The existing post-interrupt assertions are unchanged. |
||
|
|
aeb96ab0e2 |
fix(native-chat): close only the reopened chat when main closes its tab (#22922)
After /clear the current chat keeps the window tab id derived from its first session, so reopening that first session from history lands in a suffixed tab. Main closed chat tabs in the window by re-deriving that id from the session, which named the current chat instead of the reopened one: closing the reopened tab (on the desktop or from a phone) also closed the current chat and stopped its Claude. Main now sends its own tab id, as it already does for every other tab kind, and the window translates it through the host-to-window tab mapping it keeps for mirrored chat tabs. The same translation lets a host-directed focus reach the reopened tab. The close handoff marker is keyed on the id actually sent. |
||
|
|
27b823f934 |
ci: compile the E2E CLI once for all consumers (#23384)
* ci: share compiled CLI output across E2E consumers * ci: preserve CLI setup and old-ref fallback for shared artifacts * docs: record shared E2E CLI benchmark evidence * docs: include final CLI reuse timing range --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
983250a12e |
fix: await queued Codex trust preflight completion (#23148)
* fix: await queued Codex trust preflight completion * fix(agent-trust): bound the trust preflight wait and order the worktree-startup write The awaited trust write had no cap. The local Codex writer queues on the per-config.toml lane it shares with hook installs and app-server trust grants, and the SSH writer chains a resolveHome round trip plus SFTP calls over a link that may be half-open - so "start agent" could hang with no error. Cap the wait at a single deadline and continue untrusted, which only loses the ordering optimisation: the agent then raises its own trust prompt, so giving up fails closed. Also await the discarded write in worktree startup, where the PTY spawns Codex on the next line and the synchronous catch could not see its rejection. Update the reliability gate: its oracle asserted the absence of a deadline, and its manifest was left unformatted. * fix(reliability-gates): record the trust-preflight counts the cited command actually reports The gate's evidence still described the pre-deadline suite: "32 author tests", "New11pass", "original4fail/7pass". The four deadline tests this branch adds make the cited command run 15 tests in `agent-trust-completion.unit.test.ts` and 36 across the four suites, so the manifest asserted counts its own command no longer produces. Re-ran the command and recorded what it printed. Re-ran the red/green as well, against the same 15-test suite rather than the 11 it was first measured on. Pre-await: 5 fail/10 pass. Unbounded-await: 3 fail/12 pass, where the fourth deadline test — the already-complete write settling on microtasks — passes unbounded too, so it is a timer control and not a red; saying so beats counting it as evidence for the cap. Two claims the commit left stale: only the IPC handler is under test, yet the same commit also bounds the worktree-startup write, and the cap is per call site while three other awaited Codex trust writes are still unbounded. Both are now gaps instead of silence. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f5f537ef14 |
Revert "Support mouse Back/Forward buttons in shortcuts (#23287)" (#23350)
This reverts commit
|
||
|
|
5b11834d68 |
fix(editor): preserve Markdown source across rich edits (#23280)
* fix(editor): preserve Markdown source across rich edits * perf(editor): remove repeated Markdown suffix scans and block reparses * fix(markdown): retain case-insensitive editable details parsing |
||
|
|
a86fae0889 |
Support mouse Back/Forward buttons in shortcuts (#23287)
* feat: support mouse Back and Forward shortcut bindings * fix: ignore duplicate mouse shortcut presses until release |
||
|
|
408499ec58 |
Keep SSH terminals parked after their own workspace updates (#23193)
* fix(ssh): suppress workspace echoes acknowledged during stale reads * fix(ssh): preserve newer workspace observations during resync * fix(ssh): retain newer own acknowledgements during stale reads * fix(ssh): deliver peer snapshots cached by rejected patches during stale reads A stale-revision patch reply caches the peer snapshot under a new host observation token, but the renderer only records a conflict and blocks uploads. Suppressing an identical stale read then left the peer change unapplied. Only suppress when the intervening write kept the pre-read token, which is what a contiguous own acknowledgement does. --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
8416e8de10 |
refactor(persistence): retire ordinary JSON profile writes (#23202)
* refactor(persistence): retire ordinary JSON profile writes Require SQLite for writable profiles and keep import, compatibility export, and recovery in a documented legacy-json boundary. * fix(cli): preserve dynamic profile imports in release output * test(persistence): exercise SQL races and verify packaged CLI imports * test(persistence): consolidate shared fixture imports * test(persistence): close SQLite fixtures before cleanup and await launcher output * test(automations): use SQLite fixtures for dispatch fencing and skip coalescing --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
0bad8b7490 |
Better add ai notes ui (#21719)
* feat(diff-comments): draft inline notes as editor view zones Move comment drafting from floating popover to inline view zone. The draft card now appears in the editor flow, preventing overlap with code and integrating naturally with the diff layout. Includes styled margin indicator, auto-resizing textarea, and keyboard/submission handling. * Preserve inline draft comments when switching diff views - Reanchor draft zones to new models when file/line mapping changes - Disable draft mode on large diffs to maintain performance - Enhance draft card UX: shadow depth, outside-click handling, toast errors - Carry draft body and position when reopening comments * Ensure draft comment textarea auto-focuses and preserve drafts through e - Focus textarea on mount via requestAnimationFrame for reliable focusing - Add onDomNodeTop callback to focus textarea when zone reaches viewport - Preserve pending draft when editor model refreshes and re-anchor on reload - Add editor.getModel() checks before opening and re-anchoring drafts - Test that textarea is focused on creation and errors are surfaced * fix(diff-comments): prevent draft loss and duplicate submission on swap - Track submission state to prevent carrying in-flight text to new cards - Restore failed submissions for retry after model swap with unmount safety - Use effects for proper ref management per React patterns - Add isDraftOpen() guard to prevent re-opening the keyboard chord while composing - Separate concerns between user clicks and draft-open state in decorator * fix(diff-comments): show save error and restore focus intelligently - Toast error when draft submission fails, so users see why it didn't save - Return focus to editor only if the card still holds it when save completes, preventing focus theft on slow saves * minor fix * fix(diff-comments): park focus before submit button disables Chromium moves focus to <body> when a focused button becomes disabled, causing the draft zone to lose focus context. By explicitly moving focus to the textarea before the button disables, the zone can properly return focus to the editor after save. * add all translation |
||
|
|
4cafa50ec0 |
fix(windows): reuse shared PowerShell literal quoting at every hand-rolled escaper (#23083)
Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
6fc3cdcad6 |
Bundle Bun for headless Orca and profile persistence (#22635)
Bundle a pinned, verified Bun runtime for headless Orca so existing Node launch commands can hand off before opening a profile. Keep desktop execution on Electron. Add the Bun SQLite adapter and terminal backend, bounded shutdown, process inspection and cross-platform artifact qualification. Keep future managed SSH deployment separate from current production launch paths. |
||
|
|
82412dab8b |
Persist profile state in SQLite with background writes (#22612)
Migrate profile state to SQLite and move writes and backups into a background worker. Acknowledge terminal, SSH and automation changes only after durable saves. Preserve JSON import, recovery, rollback and compatibility exports. Validate migration, worker failures, maintenance, cross-profile moves and terminal lifetime races with unit, integration and end-to-end coverage. |
||
|
|
067975bfd1 |
fix(native-chat): every lease latch has a way to die (#22820)
* fix(native-chat): every lease latch has a way to die A failed exit settlement no longer leaves the lease in recovery: the release writes no stage and keeps the exit in its death evidence, and whatever the dead generation left running is settled from that evidence at the next acquire or read restore. The settlement retry flag, its disposition and every branch that read it are gone. A reservation that recorded no process is released at startup and after a failed start, the never-written conflicted status and the processless proof are deleted, recovery resolution always concludes, and Codex records its child's identity at spawn, before the handshake. * test(native-chat): a re-create needs a release proven by death evidence * test(codex): the child's pid is reported before the handshake * test(native-chat): type the crash and exit fixtures without casts * fix(native-chat): wait out a terminal owner an older build recorded, in recovery rather than manual recovery * test(native-chat): a chat mid-turn at quit reopens idle, and an older build reads an unproven release * test(native-chat): explain the baseline store cast * fix(native-chat): a terminal owner's refusal names the process instead of recursing Opening a chat whose terminal owner an older build recorded threw a stack overflow instead of the refusal that names the process to quit. * fix(native-chat): wait out a terminal owner recovery cannot verify instead of releasing it A terminal agent an older build recorded keeps its PTY across an Orca restart, so a probe that cannot answer (a start-time read that fails on a loaded host) is not evidence its transport is gone. Releasing it let a native child resume the same conversation beside the live terminal agent. Only proof of its exit now ends the claim. * ci(cross-version): run the unproven-release downgrade test The sharded unit job excludes tests/e2e/cross-version-wire, and the cross-version job runs an explicit list that did not name the new test, so it never ran in CI. A change to the record validator now also starts the job. * refactor(native-chat): map the retired manual-recovery stage to recovering at decode Nothing in this build writes manual-recovery, and restart reconciliation already rewrites it. Mapping it where the other retired handoff stages are mapped removes it from the in-memory lease type and deletes the branches that could only see it: the acquisition refusal, the renewer skip, the unproven-release stage check, and the handoff-status 'manual recovery is required' answer. Older builds accept recovering, so a record written back still loads after a downgrade. * docs(native-chat): say what happens to a live child an ownerless reservation leaves The reaper runs once at store open, while the unreconciled lease still claims the child's token, so it does not stop that child on this launch. The comment claimed it did. * test(native-chat): name the each-case label for its role * fix(native-chat): continue a create retried after recovery released its reservation The client retries a create it never heard back from under the same operation id. Recovery had released that create's reservation, so the retry was refused agent_session_ownership_unknown while its row was pending, and agent_session_operation_expired once the row aged out, and the chat never started. A retry whose lease nothing holds now continues as a fresh reservation at the next fence, which also stops the old reservation's spawn from committing. * test(native-chat): name the refusal a replayed create used to get * fix(native-chat): one quit-the-terminal-agent message for a chat a terminal agent holds A chat held by a terminal agent an older build recorded frees only when that agent exits. Sending said to reopen the chat and opening it said two runtimes claimed it; both now say the chat is open in a terminal agent, name its process, and say to quit it. Error codes are unchanged. * ci: run PR checks on the rebased head * fix(native-chat): name a terminal owner's process only when its start time can tell it from a reused pid * test(native-chat): relaunch from the dying host's durable state, so its still-pending attach cannot race the new host |
||
|
|
094fbef08c |
fix(toast): keep folder errors above standard modal backdrops (#22423)
* fix: make folder errors visible above dialogs Co-authored-by: midego <61051030+midego1@users.noreply.github.com> * test(toast): explicitly isolate background app launches --------- Co-authored-by: midego <61051030+midego1@users.noreply.github.com> |
||
|
|
7d4413b3d7 |
fix(pdf): keep the search counter in sync with selected matches (#22420)
* fix: update the PDF search counter Co-authored-by: BM Cho <bm1016bm@gmail.com> Co-authored-by: makoto-developer <72484465+makoto-developer@users.noreply.github.com> * test(pdf): respect explicitly headful launch mode * test(pdf): wait for rendered folder search highlights * test(pdf): wait for rendered text before initial search --------- Co-authored-by: BM Cho <bm1016bm@gmail.com> Co-authored-by: makoto-developer <72484465+makoto-developer@users.noreply.github.com> |
||
|
|
16784c1a67 |
fix(native-chat): name a chat write by its target, not the owner generation (#22812)
* refactor(native-chat): remove the unused terminal handoff No client ever called agentSession.requestHandoff or mounted the handoff chrome. Delete the handoff coordinator, the terminal-owner runtime, the proof write path and the unmounted UI. Keep agentSession.handoffStatus, which released desktop clients read for worktree activation, and let records an older build left mid handoff reconcile through the ordinary restart and recovery paths. * fix(native-chat): never let the pre-stop snapshot hold a chat's stop Eviction now drains delivered events before quit's resume-offer snapshot. An unbounded wait there sits ahead of the provider stop, so a sink whose journal write stalls kept the child running until the step deadline aborted the eviction. The offer is advisory: bound the drain and stop the child regardless. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop helpers only the terminal handoff called `claudeAuthEnvCarriedForward`, `isPathWithinDirectory` and `queryWindowsProcessRowsFresh` lost their last caller with the handoff. The fresh-scan tests now go through `queryWindowsProcessDescendants({ fresh: true })`, the teardown path that still depends on that contract. Co-Authored-By: Claude <noreply@anthropic.com> * docs(native-chat): stop citing the removed handoff in lifecycle comments Six comments still named the handoff coordinator, a handoff suspend, or a terminal-owned session as live participants in the flows they describe. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stalled snapshot drain without a cast Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that a start dead before proving owes no settlement The removed restart handoff test pinned this branch; nothing else did. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): keep the owner-status read behind an in-flight attach The handoff removal dropped the per-session queue from `handoffStatus`, so a read landing mid-start reported the reservation (no owner) instead of the settled chat owner, and shipped desktop clients blocked worktree activation on it. The read is queued again, as it was before the removal. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(terminal): remove the agent-session PTY write gate The gate only refused a write when a PTY had been bound to a chat session, and the only code that ever bound one was the terminal handoff this branch removes. With it gone, every admit/readmit returned "admitted" unconditionally, so the checks on the renderer write path, the runtime controller backstop, terminal.send, agent prompts, preview input and orchestration pointers, the refusal fields on terminal.send and worker-start receipts, the plugin and CLI refusal copy, and the adopted-pane orchestration routing could no longer run. Ordinary writes take the same path in the same order as before. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop the transcript helpers only the handoff called appendLegacyTranscriptMessages fed the terminal transcript catch-up and proveClaudeTranscriptBranch backed the terminal owner's exit proof. Both lost their last caller with the handoff. Their tests now go through the live entry points instead: the roster bounds through the legacy import, the pinned-read and growth tests through the ancestry replay the history window uses, and the marker rules through the string proof in their own file rather than the session-file resolver's. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): stop calling a starting chat "mid-handoff" A send refused because the chat's owner is not settled showed "The session is mid-handoff (<stage>)." in the composer. With the handoff gone, the stages that reach it are a chat that is still starting, or one whose previous agent process has not yet been confirmed stopped. The message now says which of the two it is. The refusal code is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stand-in roster decoder without a cast Co-Authored-By: Claude <noreply@anthropic.com> * refactor(codex): name the pinned rollout lookup for what it does With the terminal handoff gone, the module named codex-tui-rollout-proof holds only the pinned rollout lookup that structured Codex launches use to resume a thread, so the name described code that no longer exists. Rename the module and its options type. Also drop a mobile allowlist assertion that pinned the removed agentSession.requestHandoff method, which no longer exists to allow. * refactor(native-chat): type the owner-status reply as the host sends it The handoffStatus reply type still listed the terminal handoff's fields and states (terminal placement, host label, proof retry, queued and waiting phases, the to-terminal direction). No host writes them any more and the only client reader parses the reply as unknown, so they described nothing. The reply on the wire is unchanged. * refactor(native-chat): normalize terminal-handoff lease values once at decode Nothing in this build writes a terminal owner (`runtimeKind: 'tui'`) or the handoff's `preparing` / `old-owner-stopped` stages, but the in-memory types still admitted them, so readers across the host kept branches for values no path produces and the compiler could not point at them. The store now validates the on-disk shape, which still accepts those values so an older record is not quarantined, and maps them once while parsing: - `preparing` and `old-owner-stopped` become `recovering` - a `tui` lease becomes `native`; when it records a process it also becomes `conflicted`, the claim every build probes but never stops. A plain native owner would be stopped by restart recovery, here and in older builds. Revisions are taken over the normalized state on both sides of every compare, and the mapped record reaches disk with the store's first transaction, the same way the tab-id backfill does. The in-memory types narrow to what this build writes, and the branches that existed only for the removed values go. Structured-worker identity keeps its verdict for a former terminal owner by refusing a conflicted claim rather than a non-native kind. * refactor(native-chat): stop threading the owner kind through a reservation A reservation only ever names a native owner now, so the request no longer carries a kind and the reserved lease records `native` directly. The attach params keep `runtimeKind`: agentSession.ensure and create accept it, and the operation fingerprint stored in the ledger covers it. * test(native-chat): pin the legacy-lease rewrite with a transaction that changes nothing else Hiding a tab also committed the visibility index, so the no-op transaction wrote the file even when its open-time revision was wrong. Committing the index first leaves the pending rewrite as the only reason to write. * fix(native-chat): name a chat write by its target, not the owner generation A write carried the fence of the last frame the pane read, and the host refused it unless that fence was still current. An idle release and the restart after it each move the fence, and the release publishes nothing, so a send after a release was refused "Expected runtime fence 1; the session is at 3", and a Stop queued behind a cold start was refused as stale. Every write already names what it acts on: a send its conversation, a cancel its turn, a prompt answer its item revision, a rewind its epoch; an option is last-writer-wins. So admission stops comparing the client's fence, and the rebase that papered over one restart (admitAtResumedFence, resumedFromFence) goes with it. The writer-lease check stays, and so does the attach's compare-and-swap. Frames now stamp the fence read when each frame is sent instead of a copy each subscriber kept, which went stale on the same release. * docs(native-chat): say mutation admission checks only the writer lease * docs(native-chat): drop the send rebase from comments that still described it * fix(native-chat): keep each pane's own fence on frames so a failed restart is not resent * docs(native-chat): drop the fence from the admission the send effects run behind * docs(native-chat): give the fence move on release the reason that still holds * docs(native-chat): stop citing a write fence check in launch and mailbox comments Three places still gave the removed fence check as a reason: the launch replay said admission puts the ledger ahead of the fence, the launch surface said a send must name the lease it was admitted against, and the direct-mailbox path said the lease fence decides whether delivery is safe. Admission now checks only the writer lease. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
19d472fad8 |
fix(native-chat): report a chat's owner from its record, not its running agent (#22808)
* fix(native-chat): report a chat's owner from its record, not its running agent Released desktop clients gate worktree activation on agentSession.handoffStatus and count a chat tab as claimed only when the owner is `native`. The host answered `native` only for a live lease, and threw not_attached for a chat idle release had forgotten, so a chat at rest (idle-released, or restored after a restart) blocked its whole worktree from activating. The answer now comes from the record store for any record this host supports: the owner is the lease's runtime kind whatever its liveness, and manual recovery still answers `none`. With no map entry needed and nothing to wait for, the serialized read that kept a mid-start chat's answer honest goes too. * test(native-chat): pin the two owner answers that still refuse to vouch With liveness gone, the manual-recovery branch and the unsupported-record refusal are the only paths that keep a chat from answering native; neither had a test. * refactor(native-chat): say plainly why an unsupported chat reports no owner * test(native-chat): say what a blocked activation gate actually skips |
||
|
|
add99c908b |
fix(native-chat): send typed question answers as structured answers, not an option id (#22793)
* fix(native-chat): send typed question answers as structured answers, not an option id A typed "Other" answer was packed into the `optionId` of agentSession.respondToQuestion, a field capped at 1024 characters, so a long answer failed with "Invalid option id" and never reached the agent. respondToQuestion now carries per-question `answers` in their own field, bounded like a typed answer, and a host advertises agent-session.question-answers.v1 when it takes them. Clients fall back to the packed option id for older hosts. The host reads either form once into a typed response, records the structured answers on the resolution (and keeps the packed form older clients read), and the Claude and Codex adapters build their reply from the typed answers before the journal commits, so an answer the agent cannot take is refused rather than recorded unanswered. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): hold one answer per single-select question card Typing an answer deselects a picked option, and picking an option leaves the typed text in the field without sending it, so the card never shows two answers while sending one. Multi-select still sends picked options and typed text together. * fix(native-chat): keep keyboard tabbing from re-choosing a typed answer; accept untrimmed question ids Clicking or typing in the answer field chooses the typed answer; focus alone no longer does, so tabbing to Submit keeps the option the user picked. A question id is matched exactly by the host, so the wire no longer rejects agent-written ids with edge spaces, which older builds accepted. * fix(native-chat): choose the typed answer on click so a disabled or scrolled field cannot * test(native-chat): cover pointer events on a disabled answer field * refactor(native-chat): record the typed answer as a choice in the question card Choosing the typed answer is now an entry in the question's selection, set by typing or clicking the field and replaced by picking an option, instead of being inferred from an empty selection. Unpicking an option no longer silently chooses kept text. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
64704daac5 |
feat(feature-tips): one-time tip for agent session search (#22923)
* feat(feature-tips): one-time tip for agent session search Session search is only discoverable from Settings. Add a feature tip that existing users see once, which turns search on, shows the first index build's progress in place, and opens the sidebar search once it is ready. A toast says when a build left running in the background finishes. Also share the two-column tip layout across the voice, Cmd+J and session search dialogs, and move the CLI tip dialog into its own file. * refactor(feature-tips): simplify the session search tip after review - Watch for a background finish only when this tip closes mid-index; closing any other tip no longer arms a stray "ready" toast. - The hook detects the close itself, so dialogClosed/reset and the onStatus callback on useSessionSearchStatus are gone. - Table-driven dialog copy, reuse FeatureTipActions, and share the eyebrow badge and settings link across the voice, Cmd+J and session search tips. - One getPendingFeatureTips for the startup gate and the modal. - Demo: a phase timing table and hoisted header props. - e2e helpers mark the new tip seen too. * fix(feature-tips): retire the session search tip once the user has switched search Turning session search on or off in Settings, or enabling it from the sidebar, now marks the tip seen, the way the Voice switch does, so a user who turned search on and later off is not pitched it again. * test(ai-vault): give the legacy-filter store mock markFeatureTipsSeen Also mark the tip seen only after the sidebar's enable actually saves. |
||
|
|
d443320af2 |
refactor(native-chat): remove the unused terminal handoff (#22783)
* refactor(native-chat): remove the unused terminal handoff No client ever called agentSession.requestHandoff or mounted the handoff chrome. Delete the handoff coordinator, the terminal-owner runtime, the proof write path and the unmounted UI. Keep agentSession.handoffStatus, which released desktop clients read for worktree activation, and let records an older build left mid handoff reconcile through the ordinary restart and recovery paths. * fix(native-chat): never let the pre-stop snapshot hold a chat's stop Eviction now drains delivered events before quit's resume-offer snapshot. An unbounded wait there sits ahead of the provider stop, so a sink whose journal write stalls kept the child running until the step deadline aborted the eviction. The offer is advisory: bound the drain and stop the child regardless. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop helpers only the terminal handoff called `claudeAuthEnvCarriedForward`, `isPathWithinDirectory` and `queryWindowsProcessRowsFresh` lost their last caller with the handoff. The fresh-scan tests now go through `queryWindowsProcessDescendants({ fresh: true })`, the teardown path that still depends on that contract. Co-Authored-By: Claude <noreply@anthropic.com> * docs(native-chat): stop citing the removed handoff in lifecycle comments Six comments still named the handoff coordinator, a handoff suspend, or a terminal-owned session as live participants in the flows they describe. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stalled snapshot drain without a cast Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that a start dead before proving owes no settlement The removed restart handoff test pinned this branch; nothing else did. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): keep the owner-status read behind an in-flight attach The handoff removal dropped the per-session queue from `handoffStatus`, so a read landing mid-start reported the reservation (no owner) instead of the settled chat owner, and shipped desktop clients blocked worktree activation on it. The read is queued again, as it was before the removal. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(terminal): remove the agent-session PTY write gate The gate only refused a write when a PTY had been bound to a chat session, and the only code that ever bound one was the terminal handoff this branch removes. With it gone, every admit/readmit returned "admitted" unconditionally, so the checks on the renderer write path, the runtime controller backstop, terminal.send, agent prompts, preview input and orchestration pointers, the refusal fields on terminal.send and worker-start receipts, the plugin and CLI refusal copy, and the adopted-pane orchestration routing could no longer run. Ordinary writes take the same path in the same order as before. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop the transcript helpers only the handoff called appendLegacyTranscriptMessages fed the terminal transcript catch-up and proveClaudeTranscriptBranch backed the terminal owner's exit proof. Both lost their last caller with the handoff. Their tests now go through the live entry points instead: the roster bounds through the legacy import, the pinned-read and growth tests through the ancestry replay the history window uses, and the marker rules through the string proof in their own file rather than the session-file resolver's. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): stop calling a starting chat "mid-handoff" A send refused because the chat's owner is not settled showed "The session is mid-handoff (<stage>)." in the composer. With the handoff gone, the stages that reach it are a chat that is still starting, or one whose previous agent process has not yet been confirmed stopped. The message now says which of the two it is. The refusal code is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stand-in roster decoder without a cast Co-Authored-By: Claude <noreply@anthropic.com> * refactor(codex): name the pinned rollout lookup for what it does With the terminal handoff gone, the module named codex-tui-rollout-proof holds only the pinned rollout lookup that structured Codex launches use to resume a thread, so the name described code that no longer exists. Rename the module and its options type. Also drop a mobile allowlist assertion that pinned the removed agentSession.requestHandoff method, which no longer exists to allow. * refactor(native-chat): type the owner-status reply as the host sends it The handoffStatus reply type still listed the terminal handoff's fields and states (terminal placement, host label, proof retry, queued and waiting phases, the to-terminal direction). No host writes them any more and the only client reader parses the reply as unknown, so they described nothing. The reply on the wire is unchanged. * refactor(native-chat): normalize terminal-handoff lease values once at decode Nothing in this build writes a terminal owner (`runtimeKind: 'tui'`) or the handoff's `preparing` / `old-owner-stopped` stages, but the in-memory types still admitted them, so readers across the host kept branches for values no path produces and the compiler could not point at them. The store now validates the on-disk shape, which still accepts those values so an older record is not quarantined, and maps them once while parsing: - `preparing` and `old-owner-stopped` become `recovering` - a `tui` lease becomes `native`; when it records a process it also becomes `conflicted`, the claim every build probes but never stops. A plain native owner would be stopped by restart recovery, here and in older builds. Revisions are taken over the normalized state on both sides of every compare, and the mapped record reaches disk with the store's first transaction, the same way the tab-id backfill does. The in-memory types narrow to what this build writes, and the branches that existed only for the removed values go. Structured-worker identity keeps its verdict for a former terminal owner by refusing a conflicted claim rather than a non-native kind. * refactor(native-chat): stop threading the owner kind through a reservation A reservation only ever names a native owner now, so the request no longer carries a kind and the reserved lease records `native` directly. The attach params keep `runtimeKind`: agentSession.ensure and create accept it, and the operation fingerprint stored in the ledger covers it. * test(native-chat): pin the legacy-lease rewrite with a transaction that changes nothing else Hiding a tab also committed the visibility index, so the no-op transaction wrote the file even when its open-time revision was wrong. Committing the index first leaves the pending rewrite as the only reason to write. * test(native-chat): give the legacy-lease store test a tab id so the backfill cannot supply its rewrite The seeded record had no surface tab id, so the next open backfilled one and that rewrite alone made the no-op transaction write. The test passed with the legacy-lease rewrite signal removed. * test(worktree-activation): restore the OMP surfaced-agent resume test The handoff removal deleted it alongside the terminal-owner tests, but it covers the surfaced-PTY block that still guards resume, including an agent whose ownership is unknown. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
67fc894c8e |
refactor(orchestration): give structured sessions an orchestration actor column (#22522)
* refactor(orchestration): give structured sessions an orchestration actor column Adds nullable session:<id> actor columns to runs (coordinator) and dispatch_contexts (assignee, creator) at schema v42, a shared codec, a fill for rows that provably belong to a structured worker, and a coordinator mail-address cache that remembers a handle-less coordinator by its actor address. * test(orchestration): pin the actor columns, their fill, cache and v40/v41 upgrade paths * refactor(orchestration): fill structured-worker actors from one open-time call site * fix(orchestration): refuse terminal handles as session actors and clear the assignee actor on reassignment * test(orchestration): read the current schema version from its constant in the delivery downgrade contract The contract asserted user_version 41 after old code reopens a database current code wrote, so the v42 bump failed it. Assert SCHEMA_VERSION so the next bump cannot strand it; the pre-v41 pin and its v40 stamp stay. * fix(orchestration): count a Run's coordinator actor only at the generation it was written at A binary without the actor column rebinds and unbinds a Run by rewriting its handle and pane, which it cannot clear the actor beside. A rebind followed by an unbind leaves a row identical to a live chat binding. Both writes bump consumer_generation, which every binary already maintains, so the actor now carries the generation it was written at (coordinator_actor_generation, set in the same statement) and counts only while the two are equal. The coordinator cache, its triggers and the open-time fill read the actor through one rule in run-coordinator-actor; the fill also replaces an actor an older generation left behind. Still schema v42 (unreleased): the column joins migrate-v42 and the v42 skew-probe entries, so a database stamped v42 without it replays the chain. * refactor(orchestration): drop the unused coordinator-actor index and bare-id normalizer Nothing in this stack looks a Run up by coordinator_actor: callers load the Run and compare its current actor, so idx_runs_coordinator_actor would ship in every database with no reader. v42 is unreleased, so it leaves the migration rather than needing a later drop. normalizeOrchestrationActor had no caller outside its tests; bare session ids enter through sessionOrchestrationActor, and the handle-refusal cases stay covered there and in parseOrchestrationActor. * fix(orchestration): keep the coordinator-actor index the caller lookup needs The next step finds a caller's Runs with one statement that ORs a pane-leaf match with `coordinator_actor = ?`. SQLite splits that OR across two indexes only when both sides have one; without idx_runs_coordinator_actor the plan falls back to scanning every Run on each lookup. v42 is unreleased, so the index returns to migrate-v42 rather than needing a later schema step. * refactor(orchestration): store the Orca session id instead of an "actor" "Actor" read as a new concept when the columns only ever named a structured session. Rename them to what they hold: coordinator_orca_session_id (with its _generation), assignee_orca_session_id and creator_orca_session_id, plus the matching indexes, still added by migrate-v42 since v42 has not shipped. The columns now store the bare Orca session id rather than session:<id>. The session:<id> mail address is derived from ORCA_SESSION_ADDRESS_PREFIX where mail needs it: the coordinator address triggers and the cache refill share one SQL builder. isOrcaSessionId keeps refusing terminal-handle-shaped ids, and the generation rule and backfill evidence rules are unchanged. A dev database stamped v42 with the earlier *_actor columns replays the chain and gains the new ones. * fix(orchestration): remember every address a Run coordinator has, not the handle first The v42 coordinator triggers and the on-open refill stored one address, COALESCE(handle, session address), so a structured worker coordinator was remembered by its handle only. Remember each address the coordinator has, its handle and its current session address, each where present, so this cache follows the same rule as bindRun and no precedence is persisted. * docs(orchestration): define the Orca session id without a variable this change does not add The shared codec's comment named ORCA_AGENT_SESSION_ID, which nothing in this change defines, and ran one line past the wrap. It now says the stored id is the one the agent is addressed by (a /clear'd chat's lineage root), as the column comments do, and that PTY agents have none today rather than never. migrate-v42's note stated the lineage rule twice; it is folded into one sentence. |
||
|
|
e0144a9eb6 |
fix(native-chat): show the Codex and Claude model picker the moment a chat opens (#22756)
* fix(native-chat): show the Codex and Claude model picker the moment a chat opens A new structured chat showed no model picker until its session had been created, spawned, initialized and had answered a model listing — and the picker then listed the models a second time. Codex's listing often goes to the network, so the picker took 0.6-2 s to appear. - Keep a host-owned model catalog per agent and account home, persisted on success only and refreshed in the background once it ages out. A new read-only agentSession.modelCatalog RPC answers from it without a live session; sessions reuse it instead of listing again. - Render the picker while the launch is still provisional, showing the saved default. A pick made before the session exists is held and applied once it publishes; only the host's acceptance saves it as the default. - Mark the model and effort set in the user's Codex config as the listing's default (config/read), so the first frame names what the chat will run. - Resolve the account a record-less read would use without running launch preparation, which writes and syncs account state. * fix(codex): disable plugins in the model catalog probe app-server * fix(native-chat): read the host model catalog only for panes on this machine * fix(native-chat): name a pre-report model only for a chat this view launched * test(native-chat): pin the launch latch across publish * test(native-chat): pin a held pick reaching the host before the first send * fix(claude): pin the catalog probe's config dir by the session spawn's rule * fix(native-chat): read the host model catalog only for a visible chat * fix(native-chat): rewrite the model catalog file only when a listing changes * test(native-chat): type-check the first-send order fixture * refactor(native-chat): keep the structured options hook under the line cap * fix(native-chat): send the first turn only after every pick held during launch settles * fix(claude): name no default effort from the catalog probe listing * fix(native-chat): name no listed default model for a chat resumed from history * refactor(native-chat): let the launch own picks made before it publishes A pick made while a chat launches had no fence to go to, so the pane held it and flushed it after publish; every other sender (the outbox, the launch prompt) then needed its own gate to wait for that flush. The launch now keeps those picks in its own state, applies them against the create receipt's fence before it counts as published, and every sender follows publish by construction. The pane flush, the outbox gate and the module-wide held-pick registry are gone. The launch also snapshots the saved selection its create seeds when the intent is built, so a pick in another chat no longer relabels one still launching, and a pick the host refuses is reported the way a refused mid-session pick is. * fix(native-chat): name no default model a workspace's own config can replace The catalog's default is the account's, read without a working directory, but a chat runs in its worktree, where a project config (Codex's .codex/config.toml between the project root and the worktree, or a Claude .claude settings file that sets a model) picks the model instead. The picker named the account default there while the chat ran the project's model. A new chat's catalog read now names its worktree. The host checks that workspace for such config (existence only for Codex, the model key for Claude) and, when any is present or the workspace is not a local directory, serves the listing with no default, so the picker names nothing until the chat reports its model. * fix(native-chat): name the listed default model before the report only for Codex * fix(claude): let an option pick made while Claude starts wait for it instead of being refused * fix(codex): name no listed default when the configured model is not in the listing * fix(native-chat): show the picker as unavailable until a published chat attaches * fix(codex): keep the catalog probe's listing when config/read stalls * fix(native-chat): write the pending model catalog save before quit * chore: drop an unrelated lockfile rewrite * fix(native-chat): rename the catalog store's listing parameter off the global fetch name * chore: drop an unrelated lockfile rewrite * fix(native-chat): name the model Claude will run before its first turn * fix(native-chat): keep Claude's pre-turn applied effort out of the saved session options |
||
|
|
f559c0588a |
fix(terminal): ground a program that dies with input modes armed on the normal screen (#22739)
* fix(daemon): rebase durable checkpoints on the live terminal A durable checkpoint was folded from the previous checkpoint plus recorded output, so it inherited that checkpoint's modes forever. After a daemon restart killed a full-screen TUI and a new process started inline, the chain kept the dead TUI's alt screen and mouse tracking (?1049h ?1003h ?1006h) while the live emulator was clean. Every reattach and getBufferSnapshot served the stale chain, the renderer re-armed mouse tracking, and wheel scrolling went to a program that never asked for it: scrolling froze. Each full checkpoint is now the live snapshot verbatim (screen, layout, alt frame, modes, owner) with only the normal-buffer rows live has evicted taken from the durable replay. A checkpoint can no longer carry a dead process's modes, and checkpoints already poisoned on disk heal on the next compaction. - The first fold after a cold restore replays the same seed segments live was given, so rows line up even over a dead TUI's alt screen. - Idle zero-record folds keep the disk copy when it already agrees with live, so quit and relaunch bursts don't replay every session. - Held teardown bytes are already in the drained records and the live snapshot, so they are no longer replayed twice or appended as a tail. - The bounded getBufferSnapshot path honors the requested depth even when the live window is deeper, without phantom link rows. - The fold's ownership scanner and frame merge are removed; owner and frame come from live. * fix(terminal): one process-boundary ground for every known or proven boundary Three copies of the "the process that armed these modes is gone" reset had drifted: the cold-restore seed cleared only pen and mouse, the recovery barrier used the renderer's dead-TUI profile, and the cold-restore payload had none. A cold restore therefore left the dead process's focus reporting, bracketed paste, application cursor and keypad modes armed in the live emulator, the first checkpoint, and main's mirror. And the seed wrote the dead process's torn escape after the reset, so the new shell's first bytes could complete it (for example retitling the pane). PROCESS_BOUNDARY_GROUND replaces them: CAN, leave the alt screen without moving the normal-buffer cursor, every mouse protocol and encoding off, focus/paste/app-cursor/keypad off, cursor shown and style reset, kitty popped, SGR reset, grounded DECSC. It stays inert for the lifecycle scanner. The seed, the recovery barrier, and the cold-restore payload all use it, and the seed no longer carries the torn tail. The first fold after a cold restore now always rebases on live, because focus and keypad are not in TerminalModes and the zero-record shortcut could not see them differ. * fix(terminal): ground a program that dies with input modes armed on the normal screen The daemon's in-stream crash detector only fired when a program died with the alternate screen up. A normal-buffer program that armed mouse tracking, focus reporting, keypad or kitty keyboard flags and exited without disabling them was cleaned up only in the renderer, so the daemon kept the modes and re-armed them on the next reattach, mobile included (#13077's garbage-at-the-prompt family). The lifecycle scanner now tracks armed input modes (mouse protocols and encodings, ?1004, ?66, and kitty flags as per-screen stacks that mirror xterm's main/alt swap and its 16-entry cap). ?2004 and ?1 are excluded: shells arm them at their own prompts. Modes armed when a command starts (OSC 133;C) count as the shell's, so a prompt that leaves modes on never triggers. At OSC 133;D the trigger is now "alt screen or a program-armed input mode", still one-shot and still gated by the shell proof, and the existing PROCESS_BOUNDARY_GROUND is recorded through the stream so live and durable history change together. WSL panes spawn wsl.exe, which the shell proof does not recognise, so the detector never grounds them; a test pins that and the renderer keeps covering them. The mouse-leak e2e now keeps its arming process alive until the live pane is checked, because the daemon grounds a proven exit. * fix(terminal): keep shell- and host-armed input modes through the process-boundary ground ConPTY arms focus reporting (?1004h) before the first prompt, and the live recovery ground cleared it for the rest of the pane. The barrier now re-arms the modes that were on at OSC 133;C right after the ground, so only the dead program's modes are reset. * fix(terminal): re-assert only modes the shell or host armed outside a command A mode a program leaked past a refuted proof was still on at the next OSC 133;C, so the baseline snapshot re-armed it after a later ground. Record who armed each mode instead: only enables outside a command (before any marker, or between 133;A/D and C) form the baseline. * fix(daemon): keep OSC links and kitty flags through durable checkpoint folds and trims Stop seeding persisted OSC link ranges into the fold replay: they index the base buffer, so rows evicted by pending output left a link on the wrong text. The serializer already writes OSC 8 into the ANSI the fold replays. Re-apply kitty keyboard flags when replaying a snapshot for trimming, since rehydrateSequences omits them. Bound a smaller restore request by trimming the committed checkpoint instead of re-reading disk and rebasing the live window at a smaller depth. * test(daemon): follow the isFirstTake rename in the process-boundary ground suite * refactor(daemon): drop the unreachable deep-live branch from the durable fold The live window's override cap now derives from the restore depth, so live can never be deeper than the fold. pendingRecords and isFirstTake are required. * test(daemon): pass pendingRecords to the process-boundary ground fold * fix(terminal): reset alt-screen kitty flags in the process boundary ground Kitty keyboard stacks are per screen, so resetting only after ?1049l left a dead TUI's alt-screen flags for the next alt-screen app. Also drop the inert CAN from the ground (every site grounds after complete bytes) and correct two stale comments. * fix(terminal): track input-mode ownership in one map Each armed mode now has one owner: host (before any marker, or a prompt a 133;C proved), prompt (unproven until C), command, or stale (left past a D). Host arming is sticky, 133;D demotes command modes (the one-shot), and the ground re-asserts only host modes. Fixes a D without C triggering on host modes, an ESC c mid-command turning later enables into host modes, and a program's repeated host enable dropping host ownership. The reattach e2e now keeps the arming program alive so only the reattach reset can disarm it. * refactor(terminal): stop treating kitty flags as host state fish, the one shell that pushes kitty flags at its prompt, pops them before running a command and re-pushes at the next prompt, so the ground never needs to restore them. Only host private modes are re-asserted now. * fix(terminal): keep host input-mode ownership across RIS ConPTY answers a mid-command ESC c by re-sending ?1004h, which reset() had recorded as the command's, so the ground turned host focus reporting off for the rest of the pane. RIS now drops only non-host ownership. * fix(terminal): let only the host own focus reporting and leave it in the ground Host ownership covered every mode armed before the first marker, so a tmux that died with mouse on had it re-armed by the ground. And the re-assert's ?1004h enable made the runtime's ownership mirror revoke, so remote owners never settled on Windows. Only ?1004 can be host-owned now, and the ground skips its ?1004l instead of turning it off and back on, so injected bytes carry no enables. |
||
|
|
5e6fcca0b3 |
fix(native-chat): date a session by its own lifecycle, not its subagents' work (#22520)
* fix(native-chat): date a session by its own agent's rows, not its subagents' The journal reducer's lastActivityAt is the structured status summary's updatedAt, which the status row uses as its completion stamp and acknowledgement clock. It took the max over every journal row, and a session's subagents write into the same journal after its own agent has settled, so an idle parent was re-dated and marked unread on child work. A row now dates the session only when the session's own agent produced it: not a row whose producer linkage names a subagent, and not a subagent roster row (a subagent-group block), which the session writes but revises on every child transition. The roster rule is derived from the row body; no new persisted field. Replay folds through the same rule, so existing journals are re-dated to their own last row on reopen. Claude: a backgrounded subagent emits no child frames, so its re-dating came entirely from roster revisions (task_updated, task_notification) and from the stale-roster revision written when a journal reopens. Codex: the roster is revised on every child token-usage report; child-thread rows carry no producer linkage yet, and read as the session's own until they do. * fix(native-chat): a reopened journal's verdict on stale work does not date the session Reopening a journal settles rows the previous host left live (a working subagent roster, a live background task) to unverifiable. Those revisions were appended at the reopen moment, and a background-task row is the session's own non-roster row, so a crash-restarted session with a live shell was re-dated to the restart although no agent acted. The reconciler now writes each verdict revision with the row's own observed time. That is one rule at the one writer, covering both settle shapes; the render item's observedAt was already pinned to the row's first write, so nothing the transcript shows changes. The live-transition roster exclusion stays: live roster revisions are written by the providers, not here. The `recovered` row flag is not used as the discriminator: the live unexpected-exit settlement also writes recovered rows, and a clock rule keyed on it would stop dating a provider crash the host just observed. * fix(native-chat): date a session by the reducer's attribution of what a row wrote The clock read producer linkage off the raw row. A lifecycle batch names no row-level producer, a tombstone names none, and a revision may name none while the reducer still attributes the item to a subagent, so each of those dated an idle parent. The clock now asks the reducer: after a row applies, whether any item it wrote is the session's own work; before a removal, whether the item it removes was. * fix(native-chat): date a session's status by its own lifecycle edges A subagent writes into its parent's journal and keeps going after the parent settles. Every one of its rows advanced the summary's updatedAt, and the status row re-dated a done parent to it, so an idle parent read as newly finished and unread on each child step. The host now publishes statusStartedAt beside updatedAt: when the session's own agent entered its status, read off edges only it writes. Idle is when its newest turn ended; working is when the running turn was requested, or the earliest send still unanswered; attention is its own oldest pending ask, or a subagent's when that alone holds it. A turn that recovery settled after its host went away ended when that settle was written, so it reads as a completion the user has not seen; it carries no outcome, so no completion event or notification calls it a success. Render items carry recoveredAt, the recovered row's own write time, so nothing new is persisted. The sidebar bridge and the host ingest date the row and the main agent's clock from it whenever the row shows the main agent's own state, and keep their existing rules for a row child work holds open or a summary from an older host. The status feed republishes when the clock moves instead of on every idle row. * revert(native-chat): keep the journal clock over every row The row filter this branch put on the reducer's lastActivityAt decided which rows could date a session: a list of exclusions that each new row kind could slip past. The session's state is now dated by its own lifecycle edges, so the filter, its attribution helper and the backdated reopen verdicts go back to main. lastActivityAt, and the summary's updatedAt it feeds, is again the evidence clock over every row, including a subagent's. * fix(native-chat): keep republishing an idle session its live child work holds open A row held open by live child work is dated by when each reader saw the publish, and mobile decays a working row whose evidence is older than the staleness window. Suppressing row-activity republishes for every dated idle session froze that evidence, so a subagent running more than 30 minutes past its parent's turn made the row read idle on mobile. Only a session nothing holds open stays quiet on row activity now; its state clock is unchanged. * fix(activity): order an agent's timeline by when each state was seen An answered ask returns a settled parent to its own turn's end, so its done repeats the time of the done before the ask. Activity keyed and ordered events by that time: the new done collided with the old one and was dropped, and the row took its state from the newest-dated event, the blocked ask, so a done parent read Blocked and needed attention. Each state switch now records the `updatedAt` it was seen at. Events are keyed and ordered by that, while unread and "Clear completed" still compare the state's own time, so the answer neither re-lights unread nor revives a cleared done. The row's state comes from the pane's own status entry, so a clear that hid the done cannot leave it reading Blocked either. * test(activity): pin the timeline across repeated asks, a clear, and a stale turn Three parts of ordering the timeline by when each state was seen had no test that failed without them: - A second ask moves the answered done into history. Both dones share the turn's end, so only the history entry's own seen time keeps them apart; without it one done collided with the other and the timeline showed two Blocked events in a row. Three asks also exceed the per-pane cap, which must keep the most recently seen events, not the most recently started. - "Clear completed" on an answered row must cut off past the ask, which is dated after the done, or the cleared row stays listed. A done that the user cleared must also stay hidden once a later ask moves it into history. - A stale working row must not read as running just because the pane's own status says working. |
||
|
|
80e0bee23b |
fix(floating-workspace): keep agent launches from moving the main window's tab (#22603)
* fix(floating-workspace): keep agent launches from moving the main window's tab
Launching an agent from the floating workspace's "+" menu switched the main
window off whatever chat or editor tab it was showing and onto its terminals.
The main window's selection is supposed to move only for the worktree it is
showing: browser and editor tab creation, splits, moves and drops all check
`activeWorktreeId === worktreeId` before touching it. Two places did not:
- `launchAgentInNewTab` called `setActiveTabType('terminal')` without a
worktree, which targets the active worktree whatever worktree the launch
landed in.
- terminal `createTab` wrote the global `activeTabId` for a tab in any
worktree.
Both now follow the store rule. The launch still selects its tab within its
own worktree, which is what the floating panel renders.
The floating titlebar button had side-stepped this with an `activate: false`
opt-out plus manual selection. That opt-out had no other caller and is removed;
the button now launches and focuses like every other entry point.
* fix(tabs): scope the remaining launch surface writes to the launch's worktree
Three more launch paths create a terminal tab and then call
`setActiveTabType('terminal')` without a worktree, which targets whatever
worktree is active when the call runs rather than the one the tab landed in:
- the paired-host agent launch, after the host's asynchronous create
- Session History resume, which can target a worktree the user is not viewing
and activates it only afterwards
- sleeping-agent resume, which the activation gate runs after asynchronous
readiness checks, by which time the user may have moved to another worktree
Each now names its worktree, like the local agent launch. The new tab still
lands selected when the user switches to that worktree.
* test(tabs): pin the paired-host launch scope in its existing web-runtime test
* test(tabs): type the left-worktree resume fixture instead of casting it
* refactor(tabs): require the worktree that setActiveTabType applies to
`setActiveTabType(type, worktreeId?)` quietly fell back to the active
worktree when the caller left the worktree out. A caller acting on a tab in
another worktree (the floating workspace, a background launch, a reveal that
lands after an async step) therefore retyped whatever the main window was
showing. The launch paths fixed earlier in this branch were instances of that;
54 other callers still relied on the fallback.
The worktree is now a required argument (nullable only for the no-active-
worktree case), so every caller states which worktree it means and a new
unscoped call fails to compile. Each call site passes the worktree of the tab
it acts on; where that is by construction the active worktree (shortcuts,
palette, tab strip), the result is unchanged. `activateTabAndFocusPane`
resolves the tab's owning worktree the same way `setActiveTab` does.
End-to-end helpers that drive the store directly pass the active worktree,
which keeps their previous behaviour.
* fix(floating-workspace): let the floating New Terminal activate its own tab
The floating "+" New Terminal created its tab with `activate: false` and then
selected it with `activateTab`, because creating an active tab used to write
the main window's selected tab even for another worktree. `createTab` now
activates a tab only within its own worktree's group unless that worktree is
the one on screen, so the workaround is no longer needed.
Creating the tab active also moves the floating workspace's remembered tab to
the new one; before, it stayed on the previously selected floating tab, which
auto-acknowledge reads to decide which floating agent the user is looking at.
* refactor(floating-workspace): route every floating New Terminal through one creator
The floating "+" New Terminal had stopped deferring activation, but Cmd+T with the
floating panel focused still went through a separate creator that created the tab
inactive and activated it by hand, which left the floating workspace's remembered tab
on the previous tab. Both now call createFloatingWorkspaceTerminalTab, which creates
the tab active in its own group and focuses it.
* docs(tabs): say why an unowned tab id keeps the on-screen worktree scope
|
||
|
|
122b8c25d7 |
fix(terminal): let Linux IMEs keep the candidate key for a preedit they own (#22607)
* fix(terminal): let Linux IMEs keep the candidate key for a preedit they own Sogou on fcitx draws its preedit and candidate list in its own window and opens no Chromium composition session, so every composition-scoped candidate guard is idle when the user presses Space or a candidate digit. The selector reached the PTY as literal text and the commit arrived after it. The marker the input framework does still deliver is the claimed letter keydown - `keyCode 229` / `key 'Process'` over the physical key. A bounded window armed from that claims the next Space or digit, and stands down for a real composition session, for the commit, and on expiry. Co-authored-by: nxiaobai <48705519+nxiaobai@users.noreply.github.com> Co-authored-by: Big-ios <18501866+Big-ios@users.noreply.github.com> Co-authored-by: cporoske <14369468+cporoske@users.noreply.github.com> * fix(terminal): keep the claimed-preedit window open while the IME edits it The window released on any keydown that was not a letter the IME had claimed, but the IME claims the keys that edit and page a preedit too — Backspace, the arrows, `-`/`=` for the next candidate page — and Chromium keeps the original `code` on those 229/Process keydowns. Paging to a later candidate therefore disarmed the window and the digit that picked from that page reached the PTY. A claimed keydown now refreshes the deadline instead of clearing it. Arming still requires a claimed letter, so a bare navigation key with no preedit behind it cannot open the window from cold. * fix(terminal): release the claimed-preedit window when a composition ends An engine that does run a composition session still emits the claimed `keyCode 229` letter keydowns that arm this window, and both its commit and its cancel travel as `insertCompositionText` — the one input type the commit release deliberately ignores. Nothing else closed the window, so a cancelled preedit left it open and the next literal Space was swallowed. Refreshing the deadline on every claimed keydown had widened that tail. `compositionend` now releases it, as `compositionstart` already did. The engines this guard exists for emit neither event, so their path is unchanged. The selector suite drives the state object directly and cannot see a missing listener, so the release wiring gets its own DOM-level suite. * test(terminal): pin a Space-committing composition session end to end The regression the compositionend release fixes needs the whole session to show up: compositionstart fires once, so every letter after the first re-arms the window, the commit and the cancel both travel as insertCompositionText, and the committing Space arrives claimed rather than bare. Only the end of the session closes the window before the user's next literal Space. Fails with the compositionend listener removed. * fix(terminal): end the claimed-preedit window on the selector the IME took A claimed selector means the engine picked the candidate itself, so the round is over. It reads `key: 'Process'`, which the selector predicate cannot see, so this asks the physical code instead. Belt and braces for an engine that opens a composition session and never closes it — a documented defect in at least one shipped input framework. Without a `compositionend` the session-end release never runs, and the refresh added for candidate paging would otherwise hold the window open across the commit and swallow the user's next literal Space. Paging keys still refresh, so a long candidate browse is unaffected. * fix(terminal): let a claimed Enter or Escape end the pick too Japanese and Zhuyin commit with Enter, not Space, and Escape cancels. Both arrived claimed and fell into the refresh branch, so for an engine that opens a composition session and never closes it they held the window across the commit — the same defect the Space case was written to close, reached by a different key. Arrows, `-`/`=`, Backspace and PageUp/PageDown still refresh, because those page the candidate list rather than ending the round. Physical codes rather than `key`: `key` reads `Process` on all of them, and `Digit*` survives AZERTY, Dvorak and Colemak moving the digit row. Also corrects the window comment, which still described the orphan-keyup picking window. Since claimed keys refresh it, it is now a stuck-state valve that only expires when the IME stops reporting. --------- Co-authored-by: nxiaobai <48705519+nxiaobai@users.noreply.github.com> Co-authored-by: Big-ios <18501866+Big-ios@users.noreply.github.com> Co-authored-by: cporoske <14369468+cporoske@users.noreply.github.com> |
||
|
|
3ea15dd0a2 |
fix(native-chat): keep chats that failed to resume in the status bar and say what to do (#22448)
* fix(native-chat): keep chats that failed to resume in the status bar and say what to do After a restart, a chat whose resume did not carry on was reported only by a four-second toast that named nothing, and the status bar entry vanished because the reattach had already spent the offer. The host now files the outcome as a durable `failed` entry in the recovery capsule, with the refusal code and the prompt the offer quoted, and returns it from the restart-resume RPCs. The renderer shows a "N chats failed to resume" status bar entry, a count-only toast with Show and Dismiss, and keeps the resume dialog open with a status icon per row and a "To resume" line whose action is chosen from the reason. A failure dies on dismiss, on a successful retry, on the user's own send in that chat, or with the marker's 24h expiry. * fix(native-chat): keep the resume dialog unchanged and add failed chats as rows The failure view had replaced the resume dialog's title, checkboxes, preference box, and footer. The dialog is back to main's layout. A chat an earlier resume could not carry on is now an ordinary selectable row there, plus a status icon whose tooltip carries the reason, a dismiss control, and a "To resume" line. Selecting it and pressing Resume retries it; it is pre-selected only when a retry can succeed. Resumed chats leave the list as before. Also stubs the new failure listing on the cross-version wire host fixture, which the restart-resume RPC now reaches. * refactor(native-chat): release a failed-resume record where a send is admitted Keeps the host file at main's size, and only releases the record for a send the controller actually lets through. * fix(native-chat): note a failed restart resume in the chat and derive when it is settled The chat itself now says when Orca could not continue it after a restart, with an error (refused) or warning (unconfirmed) status row, so the failure survives the toast, a dismissed record, and another restart. A recorded failure is current only while the chat's newest user message is the one it had when the failure was filed. Listing re-derives that from the journal and prunes superseded records, replacing the in-memory set and the hook on every send. Failures move to their own optional top-level key in the recovery file, so an older build that rejects unknown entry states keeps reading its offers. A retried failure stays a failure through a rollback or a lapsed lease instead of returning as a pending offer, and the toast's Dismiss names only the chats the host listed as failed. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): check the older reader against a filed failure before any retry Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): file a failed restart resume against the chat as its attempt ended A failure was filed against the chat's newest user message read at settlement, after every chat in the action had finished. The chat's note asks the user to send a message, and one sent while other chats were still being continued became part of the filed state, so the failure stayed listed after the user had done what it asked. Each chat's newest message is now observed as its own attempt ends. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): let a reply that made a chat ineligible retire its failure When a restart resume reached a chat the user had already replied in, the attempt was refused as no longer eligible, but the failure was filed against that very reply. It then stayed listed as "finished on its own" until the user sent yet another message. An ineligible chat is no longer observed at the attempt, so its failure falls back to the reserved marker and the reply that made it ineligible supersedes it at the next listing. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): offer Retry on a failed resume only when a retry would run When the agent refused Orca's "continue" message with its own reason, the failed row fell to the generic guidance, which offers Retry and pre-selects the chat in the resume dialog. The refused message is already the chat's newest user message, so a retry is never eligible: it did nothing and the same "couldn't be resumed" toast came back. The host now reports whether a retry would run, derived at list time from the same predicate the retry applies to the failure's marker. Where it would not, the row offers Open chat and Dismiss and is not pre-selected. An older host omits the flag and the reason alone decides, as before. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): file only restart failures the user must act on A chat that stopped being resumable between listing and acting (it finished on its own or is waiting on the user) was filed as a failure with no note in the chat. It now just spends the offer. A failed reattach now writes the same in-chat note as a refused continuation, so every filed failure explains itself in the chat. An unconfirmed continuation's failure retires once the chat shows the continuation's own message opened the newest turn. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): clear a failed resume from the status bar once its chat moves on While a failed resume is listed, the restart store watches the host's status feed; when a failed chat's status or latest prompt changes after the list was read, it re-reads the host once. The host still decides whether the failure stands. Nothing is watched while nothing failed. The failure toast now counts only the requested chats the host still lists as failed, keeping the old count for a host that sends no list. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that an unjournaled continuation never retires its unconfirmed failure Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): stop telling the user to send a message into a chat Orca couldn't reconnect A failed reattach, or a continuation refused because another window or terminal owns the session, now leaves a note saying Orca couldn't reconnect the chat instead of advising a send that would meet the same refusal. The restart list keeps the reason-specific advice. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): keep the failed-chat re-read from undoing an action or missing a reply The status-bar re-read no longer runs while a resume or dismiss is in flight, and its answer is dropped if one settled meanwhile, so a dismissed failure cannot come back. A change to a failed chat already seen always triggers it, whatever the host's timestamp says. A chat whose unconfirmed continuation the host already retired is now reported as resumed instead of saying nothing. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that no failed-chat re-read runs under a resume in flight Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): typecheck the unjournaled-continuation case against a nullable marker Co-Authored-By: Claude <noreply@anthropic.com> * docs(native-chat): drop the stale "no arguments" note on the restart-offer params The dismiss call now names sessions, so the older comment contradicted the schema below it. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): count unconfirmed resumes apart from refused ones in the action toast The post-action toast said "N chats couldn't be resumed" for chats whose continuation may well have gone out, while the list and the chat itself say Orca couldn't confirm it. That wording invites a duplicate "continue" send. Unconfirmed chats now get their own count, classified by the outcome the host filed, so the toast matches the row it points to. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): stop offering Resume on a failure the host says cannot retry A failed row the host marks unretryable could still be ticked, sending a resume that could only fail again; its checkbox is now disabled and it never joins the action. An older host that omits the flag keeps today's selectable row. The status bar no longer calls a chat "failed to resume" when the host only couldn't confirm the resume, matching the dialog's own wording, and the mixed toast's second line now says "other" so it cannot read as the same chat. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): skip unreadable failure records instead of rejecting the capsule A failure record this build cannot parse (a newer outcome, say) made the whole recovery file unreadable, so a downgraded build listed no restart offers and could not record new teardowns. Failures are advisory: parse each one on its own and drop what does not parse. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that a failure record with an unreadable marker is skipped The skip-unreadable-failure test only covered an unknown outcome, so going back to the throwing marker parser for failure records still passed. A failure record usually outlives its offer entry, so a newer marker shape can appear only there. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5e3effc32f |
fix(native-chat): show every user message on the message rail, not just loaded ones (#22558)
* fix(native-chat): show every user message on the message rail, not just loaded ones The message rail was built only from transcript rows the renderer had loaded, so any prompt above the loaded page had no tick, and the rail lost ticks when a long live session trimmed its retained window. The host now answers `agentSession.conversationOutline`: every user message in a structured session's journal (item id, creation sequence, a preview cut to 200 characters, image count) plus the journal position it is current through. It is derived from the reduced journal on each request with the same projection the transcript runs, so an entry's id and preview are what the loaded row shows. The reply is bounded like a history page: previews shorten, then drop, and only then do the oldest entries go. The renderer asks only while the pane is visible and older history is unloaded, uses outline entries only for messages older than its loaded window (the window is authoritative for the rest), and falls back to loaded messages while the outline is stale (epoch change, or the window trimmed past what it covers). Selecting a tick with no row pages older history in until the row exists, then uses the existing rail jump. The method is negotiated with `agent-session.conversation-outline.v1`; a client never calls a host that does not advertise it, and any failure leaves the rail on loaded messages. * fix(native-chat): keep a rail jump from the bottom from re-arming follow and cancelling itself A rail jump started by a reader following the end stopped a few pixels above the bottom instead of reaching the message. Paging older history in for a jump always leaves the reader following at the very end, so jumps to unloaded messages hit it every time; a jump to a loaded message from the end did too. The jump scrolls smoothly, and only its landing is marked as the application's own scroll. Its first frames move a pixel or two, still inside the band where a reader event re-arms follow, so the list read the jump leaving the end as the reader arriving at it. The next frame, just outside the band, then read as the reader taking over and rebased the view with an instant write, which cancels the smooth scroll. Re-arming follow now needs the reader to be arriving at the end: an unmarked event that moved the view up never reattaches a detached reader. * test(native-chat): check a rail jump left the end before reading where it landed * fix(native-chat): keep the rail's message list still while a press selects an item With the whole conversation in the rail, its hover list overflows and opens scrolled to the message being read. Clicking an older item did nothing: pressing it focuses it, which turns the hover preview interactive, and that switch re-ran the effect that scrolls the lit row into view and focuses it. The list moved under the pointer between press and release, so the click landed on the list instead of the item, and focus jumped to the lit row. Revealing the lit row now follows the list opening (and its rows shifting), not the switch between hover and interactive. Entering interactive moves focus into the list only when focus is not already on one of its items. * perf(native-chat): keep the rail's outline entries stable while a trimmed window slides A long live session holds a head-trimmed window, so every new row moved the oldest-loaded edge and rebuilt the outline view even when no user message crossed it. The rail then re-merged, re-rendered and re-read the scroll geometry on each new row. The view is now reused while the set of entries older than the edge is unchanged. * fix(native-chat): let a rail jump wait out an older page already loading Scrolling to the top of the loaded window asks for the next older page. A rail jump made while that page was in flight asked again, got the lane's immediate no-op return, read it as a page with no progress, and dropped the click. The jump now waits for the in-flight page to land before deciding. * fix(native-chat): reattach follow when content shrinking clamps a reader onto the end The rule that stops a smooth scroll leaving the end from re-arming follow compared offsets, so it also refused a reader whose offset dropped because settled content folded away beneath them and the browser clamped them onto the end. They sat at the bottom without following, and the next reply grew out of view. Re-arming now requires closing on the end rather than moving down, which still rejects a scroll leaving it. * fix(native-chat): keep the rail hooks' ref writes out of render Both hooks wrote a ref while rendering, which React may replay or discard. The rail's structural-sharing baseline is now recorded after commit, and the history jump calls the lane's page loader from its effect instead of through a render-updated ref. * fix(native-chat): let the latest rail pick win over a jump still paging A jump to an unloaded message keeps paging older history until it lands. A pick made meanwhile lost to it: a loaded message scrolled into view, then the earlier jump finished and pulled the reader away; another unloaded message was ignored. Picking a loaded message now cancels the paging jump, and picking an unloaded one retargets it without asking for a second page. * fix(native-chat): step a rail history jump with a functional update The step that requests the next page wrote the pending jump from the value its effect closed over, so a pick or cancel queued since that commit would be overwritten. * refactor(native-chat): run a rail history jump as one abortable awaited loop The jump through unloaded history was an effect-driven state machine that guessed "no progress" from the message list's identity and could not be cancelled by anything but another rail pick. A diff reveal, "Jump to latest" or the reader scrolling left it paging, and when its page landed it pulled the reader away; a history read that kept failing during a live turn could repeat back to back. Loading an older page now reports how it ended, and a second request while a page is in flight joins it instead of being refused. The jump is an awaited loop that reads the rail from a commit after each page, stops on anything but a page that moved the window, and is aborted by any other navigation, reader input (wheel, touch, scroll keys, scrollbar), a session switch or unmount. The latest pick wins. * fix(native-chat): derive the rail outline from the transcript's own projection The host built the outline from user items alone, while the transcript orders every message by when it was observed, folds tool results into the turn above and then drops harness turns. An imported user row carrying a tool result beside harness text therefore got a rail tick previewing the harness text, and clicking it paged history for a row that never draws. The transcript's order-fold-strip projection now lives in one shared function. The renderer's list projection wraps it with its own tail-row order, and the host runs it over the whole journal and keeps the user rows that draw content, so the outline lists the same messages in the same order. * fix(native-chat): retry a failed rail outline read a few times A failed outline read left the rail on loaded messages until a new gap opened, the pane was shown again or the epoch changed. The client now rejects a failed read (a host without the outline still resolves to nothing, without being called), and the rail retries up to three times with doubling backoff. * test(native-chat): give the rendered-transcript fixture the older-page result contract * perf(native-chat): sort the shared transcript projection without a spread copy * fix(native-chat): let a wheel over the rail cancel a rail jump still paging The rail forwards its wheel to the transcript, so a reader scrolling there is scrolling the transcript. That wheel never reached the scroller's reader-input handlers, so the jump kept paging and later pulled the reader to its target. * fix(native-chat): keep the rail's list open while a picked message pages in Picking a message that is not loaded yet can take several pages of older history. The list closed on the pick, so its busy item was never seen and the click looked ignored. The list now stays open with that item pulsing until the jump lands or is abandoned, and stops revealing the lit row meanwhile so the rows do not move under the pointer. * fix(native-chat): keep the shared transcript projection loadable on mobile The projection moved to src/shared, which mobile's Hermes engine also loads, and switching its sort to toSorted broke the Hermes compatibility guard. Sort a copy made with Array.from instead, as other shared code does. |
||
|
|
90f0c5c8ae |
test(file-search): pin request-key listings against the real runtime shape (#22312)
* test(file-search): pin request-key listings against the real runtime shape * test(file-search): pin intermediate renders and late remote answers Strengthen the stale-answer guard to assert every intermediate render reads as loading (null), add a late-answer drop case, make the tab-entry loading pin non-vacuous, and correct the e2e comment for local listings. * test(file-search): keep only the non-duplicate runtime-listing pins Drop the remote projection cases already covered at the hook level, collapse the classifier integration to the loading pin, drop the local-only rapid-edit e2e, and fix the brittle README absent-file assertion that failed CI. |
||
|
|
563dd5487f |
feat(native-chat): show a Codex chat's goal above the composer, and set it from goal mode (#22377)
* feat(native-chat): show a Codex chat's goal above the composer and set it from goal mode Structured Codex chat now treats the thread goal as session state: a banner above the composer shows the current goal (pursuing / paused) with clear, pause/resume and expand; /goal enters a goal mode whose send calls thread/goal/set; the objective is journaled as a user message marked as sent as a goal. The banner is derived from the journaled goal rows, which Codex's resume snapshot refreshes, so a reopened or adopted chat shows its goal. Fixes STA-8159 * fix(native-chat): replace a recorded goal by clearing first, and recover a lost goal-change response - A set while the journal records a goal (any status) clears it before setting, so the new goal starts with its own time and token counters instead of rewriting the old goal's objective in place. - The threadGoal plan answers an unknown outcome from the goal the journal records and reruns otherwise, so one request timeout no longer refuses every later Clear/Pause/Resume as unknown for the mounted session. - The goal-mode chip says "Exit goal mode"; "Clear goal" stays the banner's action on the provider goal. - A typed bare /goal on Enter enters goal mode, the same as picking it. - The renderer reads the goal off the tail of its ordered snapshot; the host's unordered map keeps the by-sequence reader. - Drop the composer's duplicate in-flight guard; the goal controller already serializes changes. - Pin that a counter-only revision reaches a subscriber's live page under its original sequence. * fix(native-chat): keep a bare /goal inside goal mode as the entrance, and pin goal delivery and serialization - A bare `/goal` submitted while already in goal mode re-enters the mode instead of setting a goal whose objective is the literal text "/goal". - The counter-only revision pin now drives the host's own event sink bound to a real journal, so it goes red when the publish after a lifecycle transition is dropped; the previous fake sink never published. - Pin that a set which threw after journaling its objective puts that objective back exactly once when the ledger reruns the same operation id. - Cover the goal controller hook: absent without host support, the loaded window wins over the host's answer, a second change while one is unsettled answers false without a request, and a refused change frees the next one. * fix(native-chat): resume a blocked or usage-limited goal, and keep goal-mode drafts honest - The goal bar offers Resume on a blocked or usage-limited goal, which the provider resumes exactly as it resumes a paused one; a goal whose token budget is spent still offers only Clear. The rule lives beside the other goal facts in shared code so every reader answers it the same way. - A `/goal <text>` typed inside goal mode sets the objective `<text>`, as it does outside goal mode, instead of a goal whose objective is the literal command. - Setting a goal is a host round trip; a draft edited while it was in flight is no longer wiped when the goal lands, matching every other host command. - Pin that a lost status-change response is read as applied only when the recorded goal is in that status, that a cleared row in the loaded window outranks the host's earlier answer, and that the PTY lane is untouched. * fix(native-chat): keep the load-older anchor on the loaded window when a live revision lands below it A live revision of a row keeps that row's original sequence. When the row is older than the client's loaded window, the shared reducer merged it in and it became the load-older anchor, so paging `before` it skipped every row between. A goal's counter-only revisions during a long goal turn reach any client that attached after the goal row left its window, so a reopened chat lost rows on scroll-back. The reducer now admits live rows only at or above the window's oldest row while older rows remain on the host; the journal keeps the revision and the page reader serves it once the window reaches the row. With nothing older on the host the window is the whole journal, so a row below the head is admitted as before. Also drain accepted provider events before a goal set reads the journal to decide whether it replaces a recorded goal. |
||
|
|
4f814c1dd4 |
fix(browser): scope floating browser shortcuts to their own panel (#22361)
* fix(browser): scope floating browser chrome shortcuts to their own panel The floating panel rendered its browser with the default 'focused' scope, so a focused split browser and the floating browser both answered chrome chords. The floating browser now answers only chords from inside its own overlay, and a focused split yields chords whose target sits inside the floating panel. * test(e2e): pin floating browser shortcut scope * test(e2e): reuse split shortcut fixtures in the floating scope spec |
||
|
|
dfff3915c4 |
fix(browser): scope back/forward/reload/zoom/grab shortcuts to the originating split (#22340)
* fix(browser): scope back/forward/reload/zoom/grab shortcuts to the originating split With two browser panes visible in a split, Back, Forward, Reload, Hard Reload, page zoom and Focus Address Bar fired in every visible pane. Main forwarded these guest chords without the page id, and each split's active pane subscribed. The renderer-side listeners for the same chords were also window-wide per pane, so a key pressed in the toolbar (or in a terminal in another split) reached every active browser pane. Guest-forwarded chords now carry the originating browserPageId; preload admits only well-formed payloads and each pane ignores ids that aren't its own. Toolbar-path listeners use the same focused-split scope Find already uses. The streamed remote pane's history chord moves onto that scoped hook. Cmd/Ctrl+C grab (STA-3319) gets the same scope and no longer arms while a text selection exists outside the browser pane, so copying from the native chat transcript works again. * refactor(browser): simplify split shortcut scoping per review Drop the preload payload admission (main and preload ship together), fold the three inline scope checks into browserChromeShortcutOwnsEvent, and replace the outside-overlay selection check with a plain live-selection rule so Cmd+C copies from surfaces that do not move split focus. * refactor(browser): share one zoom command type and tidy shortcut comments BrowserPageZoomEventDetail and BrowserPageZoomCommand were the same shape; keep one in shared/browser-page-zoom.ts and route guest and local zoom through a single handler. * refactor(browser): narrow the zoom event with instanceof instead of a cast * test(e2e): pin split-scoped browser shortcuts Two browser splits (and a terminal beside a browser) now prove that Back, Forward, Reload, Hard Reload, page zoom, Focus Address Bar, and the element grab chord act only on the split that sent them, from both the guest page and the browser toolbar. A native chat selection proves Cmd/Ctrl+C copies instead of arming grab. Split fixtures move to a shared helper so both specs reuse them. |
||
|
|
c3c4b4ec02 |
fix(browser): notify state on offscreen page navigation commit (#21703)
* fix(browser): notify guest state on offscreen page navigation commit Offscreen pages have no renderer to publish their row, so the navigation commit is the only moment paired clients learn the new URL. Every failure path already announces; the success path was missing this notification, preventing background links from mirroring. Extend the E2E test with helpers to verify background links mirror before surfacing as panes, reading both client and host state to isolate failure causes. * test(e2e): validate host tab response in link-open routing Replace unsafe type assertion with runtime validation of the response structure. Add defensive checks to ensure tabs exist and have the expected shape before processing, improving test robustness when the remote host response is incomplete or malformed. |
||
|
|
8307dc5d7b |
Fix ai-vault-panel-search test for updated consent UI (#22093)
The consent dialog no longer provides a 'Not now' button; only 'Enable' is available. Removed the test's interaction with the obsolete button and the input clearing/refilling that followed it. Also includes E2E failure triage report documenting nine product issues and their associated Linear tracking. |
||
|
|
35fe67b610 |
fix(perf): measure terminal latency with presented CI frames (#22096)
* fix(perf): present benchmark frames only on isolated CI display * fix(perf): wait for the benchmark page before presenting its window * docs(perf): record full scale pass with unchanged latency budgets * test(perf): document and verify the isolated display exception |
||
|
|
2739246058 |
feat(native-chat): light the unread indicators when a structured chat finishes (#21924)
* feat(native-chat): light the unread indicators when a structured chat finishes A structured native chat had no attention producer. The PTY lane reaches the unread markers through use-notification-dispatch, whose liveness reads PTY state and whose admission requires terminal panes, so a structured session — which runs on the execution host with no renderer PTY — could finish a turn with nothing lighting anywhere. A backgrounded chat was the worst case: with no mounted pane there was no reader to notice at all. The host derives the completion, because only the host can. The journal keeps committing whether or not a renderer holds a reader, so the new feed observes each commit at StructuredAgentSessionClientDelivery.publishJournal and emits on every running -> settled transition. That edge runs after the subscriber loop and independent of it, which is exactly why a chat nobody is watching can still complete. It is a separate capability-gated stream rather than a field on the status summary: the summary carries no turn identity and no outcome, and is re-broadcast on every status change, so folding a completion into it would make every status consumer a completion consumer. ONLY `success` LIGHTS ANYTHING. Outcome is A0's provider verdict and is never inferred: a turn the host merely watched stop carries no outcome and produces no event, because absent means UNKNOWN. `completed` alone proves nothing — a provider reports its own API error as a finished turn — so the host emits nothing for it and the renderer filters again on the way in. RECOVERY IS LIVE-ONLY. Nothing is retained, queued or replayed on either side. A subscriber learns what settles while it is subscribed and nothing else; on reconnect it re-opens an empty stream and whatever landed during the gap is gone. A retained completion would be a durable "unread is owed" obligation with nothing to retire it, and a reconnect would then light the dot for work the user already read. Tests on both sides pin this so a later refactor cannot quietly turn it into catch-up. The dot itself reuses the neutral policy in attention/agent-attention-policy and #21274's structured surface adapter, so suppression, acknowledgement and addressing keep exactly one implementation and the surface key is never omitted to evade a check. No second suppression rule is introduced. OS delivery is deliberately not wired: this calls applyAgentAttentionUnread, not applyAgentAttention. Also narrows the completion feed's journal dependency to the newest-turn reader it actually uses, and adds journal.newestTurn() beside the existing activeTurnId() on the one shared by-sequence scan rather than a second scan. * test(cross-version): register the turn-completion subscribe on the wire manifest The cross-version gate asserts the structured surface's method list by name and count, so an additive method has to be declared there deliberately. Adding the entry makes the suite call it in both skew directions and stubs the host side, which is the statement the gate exists to force. * fix(native-chat): rebaseline completion feed after rewinds |
||
|
|
83a031c081 |
refactor(agent-status): delete two launch-config accessors left with no callers (#22032)
#21844 removed the Codex launch-argument attention suppressor, which was the last production caller of two launch-config lookups. Both were left in place for a follow-up; this is it. `getAgentLaunchConfigForStatusMetadata` (renderer store) looked a launch config up from a loose metadata bag. Its sibling `getAgentLaunchConfigForStatusEntry` takes a real status entry and still serves the one live consumer, cold restore resume startup. Deleting the metadata accessor also orphaned its `getLaunchConfigForStatusMetadata` helper and the `AgentLaunchConfigStatusMetadata` parameter type, so those go too. `getAgentStatusLaunchConfigForPaneKey` (main runtime) returned a pane's launch config behind a launch-token fence. Its two remaining references were assertions in the launch-authority retirement test. They were a second view of a state bit the test already pins: retirement nulls `pty.launchToken`, and the surviving `verifyOrchestrationCompatibilityCaller` assertion fails when it does not. Verified by ablation — disabling only the `launchToken` nulling fails that assertion with the accessor already gone, so no coverage is lost. Retirement never cleared `launchConfig` itself, so there was no second property hiding in those assertions. Test mocks that existed only to satisfy the removed store method are stripped; the tests themselves are about other behaviour and stay. No behaviour change. |
||
|
|
98299d879b |
fix(terminal): persist a parked remote pane's scrollback across a hard restart (#21295) (#21367)
* fix(terminal): route a parked pane's scrollback patch to the remote host's partition A park capture changes only terminalLayoutsByTabId, so its debounced session patch carries no tabsByWorktree. splitWorkspaceSessionByHost built its tab->worktree index from the patch alone, resolved nothing, and routed every layout to the 'local' partition, where main's pruneLocalTerminalScrollbackBuffers strips scrollback it cannot attribute to a remote worktree. The remote host's runtime:<id> partition never received the capture, so anything parked since the last clean checkpoint was lost on a crash, SIGKILL, or a forced kill during an app update (#21295). Route tab-keyed patch fields with the renderer's live tab catalogs as a fallback when the payload names no tab rows. Payload rows still win, so full-payload writes are byte-identical. Once routed to runtime:<id>, main merges the partition's own prior tabsByWorktree and the prune preserves. Proven by tests/e2e/paired-remote-terminal-parked-scrollback-restart.spec.ts: a hard kill (no checkpoint) then relaunch, asserting the capture is in the remote host's partition on disk. Mutation: reverting the routing fix turns that assertion red and fails the 3 catalog-dependent unit routing tests. (cherry picked from commit |
||
|
|
9324bb8137 |
fix(editor): preserve Markdown preview when following wiki links (#19790)
* fix(editor): preserve preview when following wiki document links * test(editor): avoid cast in markdown navigation fixture --------- Co-authored-by: Neil <neil@stably.ai> |
||
|
|
ffb79c71e0 |
fix(editor): open plain details blocks in rich markdown mode (#19784)
* fix(editor): allow plain details blocks in rich markdown mode * fix(editor): preserve case-sensitive details class values |
||
|
|
438744ca77 | fix(opencode): preserve global config discovery (#21854) | ||
|
|
72d61c459f |
fix(e2e): wait for terminal remount after golden worktree switch (#21837)
Mac release goldens failed after switching back to the original worktree: sidebar aria-current landed while the store still pointed at the child tab, so waitForActiveTerminalManager timed out. Wait for activeWorktreeId, force the terminal tab visible, and restore this spec from the workflow ref so older cut SHAs pick up the harness. |