* fix(runtime): main's relisting for a close carries main's tab layout, so a pane close stays a pane close (dark, layout-owner build only)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(layout-owner): the relist touches only a row it added, with its own copy of the layout
* fix(layout-owner): store the relist's layout only when it changed anything
---------
Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
* feat(agents): configure Auto terminal permissions per supported agent
* feat(agents): restore global permission switch with Auto, per-agent exceptions
Replace the Set defaults menu and inline per-row dropdowns with a global
Manual | Auto | Yolo switch. Auto reveals an Other agents control beneath it,
mixed states badge exceptions, and each row's expanded panel owns its
per-agent permission choice.
* test(e2e): fail loudly when the store is unavailable in the Auto permissions spec
#29150 removed one layout write from tabs-group-actions.ts without pruning
its baseline entry, so every PR's static analysis fails with 'baseline still
allows 13'.
* test(browser): temporary timing probe for the popup first-frame flake
* test(browser): extend the temporary popup flake probe to cold caches and the main process
* test(browser): wait for the popup fixture's first paint during startup, not in the first test
* test(browser): name the page whose first frame never arrived
---------
Co-authored-by: E2E Test <e2e@test.local>
* fix(diff-comments): a review note on a twin worktree saves to the open copy and names its host
* fix(diff-comments): rendered notes and queued saves follow the open twin
* fix(diff-comments): one host-qualified copy per change; async delivery finds its copy by note id
* fix(diff-comments): a copy is its row's own host; unstamped rows keep the owner their repo names
* fix(diff-comments): the open host picks a twin by the owner index and never falls back to another host's copy
* fix(diff-comments): a worktree's saves stay in one queue when its row gains a host stamp
* fix(diff-comments): one queue and floor per worktree id; paired servers stay separate copies
* refactor(diff-comments): keep main's queue and floors; scope rows only when two hosts publish the id
* fix(diff-comments): per-twin rollback floors that follow the mutation chain; write module split out
* fix(diff-comments): a queued save finds its copy by the list its chain produced
* fix(diff-comments): name the row's own host on every save, and treat a refused save as failed
* fix(diff-comments): a queued save keeps its rows' identity even before a twin appears
* fix(diff-comments): a queued save follows its row when a listing stamps it with the write's host
* fix(diff-comments): refuse a write when the open host's copy isn't listed and another host's is
* fix(diff-comments): a lone unstamped row's queued save survives a stamping refresh
* fix(ssh): move the managed tunnel's is-current check beside its record (fix red main)
orcad-managed-tunnel-manager.ts reached 301 counted lines when #30891 landed on
#30888, failing every PR's static analysis. The six-field check that the recorded
tunnel already matches the connection and deployment moves, unchanged, into
orcad-managed-tunnel-active.ts, which owns that record.
* style: format the 22 files main merged unformatted (fix red main)
CI's format check fails every PR on these files. Formatting pushes
NativeChatStructuredSession.tsx one line past its 400-line limit, so its one-use
question list is inlined where questionBody is already non-null (identical
behavior: agentSessionPromptQuestions is pure and ran on every render).
* fix(renderer): host writes route only through the strict owner, and twin projections stay on their host (review-5 #2#9#11)
A selected ssh: host that two paired servers proxy now resolves ambiguous at the one
selected-host resolver, so no caller can fall back to this app's own SSH connection.
File Explorer and SSH mutation expectations derive the full {endpoint, place} pair from
resolveOwner, so a server folder's nested SSH target stays the write's host. A local
lineage write projects onto its own host's rows only. The id-only store-write ratchet
now also covers persistence repo-row lookups by id alone (review-5 #10's shape).
* fix(ratchet): the id-only lookup scan also sees optional chaining and destructured ids
* fix(renderer): an unstamped folder keeps reading its host from its repos; editor focus fallback left for a follow-up
* docs(renderer): the File Explorer owner comment names its folder fallback
* fix(linear): show a saved Linear credential that cannot be decrypted
The new-workspace Linear search treated a decrypt failure as an empty result, so a connected workspace still said to start typing. Record the existing decrypt message and offer Settings.
* fix(linear): open Linear settings without an Active Server read
The settings helper read activeRuntimeEnvironmentId, which the focus-read check counts as the Active Server setting. Resolve the source row's host id instead, which is the same host the helper already used.
* fix(linear): carry a Linear read's credential error to the composer
The composer showed "Start typing to search Linear issues." when the saved Linear credential could not be decrypted. The previous attempt wrote the read failure into the host-wide Linear status, which describes the Active Server and every workspace on it, while composer reads run on the repo's host and selected workspace. That caused search loops, blocked healthy hosts and workspaces, and a latch that stopped retrying.
Now Linear search and list reads reject with the plain decrypt message instead of resolving empty, the composer's URL lookup asks issue reads to do the same, and the composer keeps that error per request: it clears when the next read starts and shows in the Linear and Smart tabs (and the attach-a-link search) with a Settings link that opens the Linear card on the read's host. Nothing writes read failures into the shared status any more. The Tasks page keeps showing no rows plus the error, as before.
* fix(linear): keep attach actions and a steady error on Linear read failures
- Type the composer test's repo fixture so CI's typecheck passes.
- Attach a link: a Linear read error no longer hides the status row. The Jira-URL mode switch and the Linear-URL Retry stay; only the duplicate sentence is dropped when the empty list already shows it.
- Composer: the status refresh after a first credential failure re-runs the same read. Keep the error on screen through that re-read instead of flashing a loading skeleton.
* fix(linear): keep the attach status row in place; share the integrations Settings path
- Attach a link: keep the status row mounted (empty) when the result list already shows the Linear read error, so the Reference type button doesn't jump across the row.
- Composer: Jira and Linear Settings buttons now share one path that picks the source's host with getTaskSourceRuntimeSettings, so the Linear one no longer re-implements it. No behavior change.
* fix(linear): make the Linear Settings action a list row; keep Jira URLs on the Linear tab quiet
- Composer: the credential error and its Settings action are one list row (red sentence plus "Settings"). It fits the capped results popover and is reached with the arrow keys like every other row; Enter on it opens Settings. It is never highlighted by default, so a plain Enter still moves to the agent picker.
- Attach a link: on the Linear tab, a pasted Jira URL shows only the existing "Select Smart or Jira" status, as before; the Linear search of the URL text no longer adds a red Linear error beside it.
* fix(linear): show the Linear read error as one plain line with no Settings action
The Settings action inside the results list needed its own highlight and Enter rules and still could not fit the capped popover. The list now shows one compact red line, in the Linear and Smart tabs and in attach-a-link: "Orca can't read your saved Linear login. Reconnect Linear in Settings to search issues." It names the fix in the same words as the neighbouring not-connected hint. The highlight resolver, the input's Enter handling, the composer's Settings handlers and the prop plumbing are back to main.
* fix(linear): show an unreadable Linear login as a Reconnect Linear empty state on the Linear tab only
The composer's Smart tab no longer mentions Linear's login; the Linear tab, Edit workspace links and the Tasks page show a calm title and sentence where results would be, matching the not-connected state.
* fix(linear): drive the Tasks Reconnect card from the selected scope's read; plain words in workspace notices
Host-wide status flags any broken workspace, so a healthy selected workspace with no results no longer shows Reconnect Linear. Projects and Views notices say the unreadable login in plain words.
* fix(linear): keep the Tasks Reconnect card up while the same list re-reads
The list effect cleared its error on every re-run, so a refresh flashed the raw status bar until the read failed again. Clear it on success instead.
* refactor(linear): drop the unreachable typed-row padding from the Linear tab's Reconnect state
The Linear tab never has a typed-text row, so the state always uses the roomy padding.
* fix(linear): derive the Tasks Reconnect card from the plain list's read error in the board
Avoids widening the selection model's existing type assertion; project and view tabs report errors inside their results.
* fix(linear): say the unreadable Linear login in one short line
* fix(orcad-migration): a source fenced on a manifest no request can carry can be aborted by its identity
A desktop that fenced a manifest just under the export cap before #30836
could never send its abort: every catalog request carries the whole
manifest and refuses it before sending. The abort now asks the server by
migration id and digest instead (orcad.migration.abortCatalogByReference,
behind a new capability), and releases only on the server's durable
absent answer. A committed server still refuses the release.
Co-authored-by: Claude <noreply@anthropic.com>
* test(orcad-migration): cover the durable abort by reference and its RPC method
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
setWorktreesPinnedAndReveal took bare worktree ids and looked each up with
getKnownWorktreeById(id) and no host, so pinning one card of a twin (the same
worktree published by a local and an SSH host) could pin the other copy.
The action now takes host-qualified targets and skips a host-less id that two
hosts publish. The sidebar Pinned drop resolves its ids through the drop
resolver (dropWriteRows), so it pins the dragged copies; the drop-commit
context hands the active drag's copies to the pin commit. The context-menu pin
passes the clicked row.
* fix(runtime): a terminal spawn on an SSH twin no longer wakes or is refused by this computer's sleep
The host's sleep state was keyed by the bare worktree id, which a repo
registered on two hosts publishes twice. A spawn on the SSH copy found the
local copy's sleep, so an automatic probe was refused and any other spawn
woke the local sleep. Key both sleep maps by host plus workspace, with the
sleep side deriving its host the way a spawn does.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(runtime): a runtime-stamped project's sleep keys on its nested SSH target, as its spawns do
Co-authored-by: Claude <noreply@anthropic.com>
* fix(runtime): derive the sleep key from the launch-host resolver the launch scope uses
Predicting the host from one repo row disagreed with the launch path for a
runtime-stamped project with a nested SSH target. Reuse resolveWorktreeLaunchHost
so a sleep and the spawns of the same workspace always name one key.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(runtime): a sleep stops and files only its own host's terminals, and refuses an unresolvable host
Sleeping this computer's copy also stopped the SSH twin's live PTYs and filed
them under this host's key, where the twin's spawn could never wake them.
Resolve one sleep scope (key and PTY fence) per sleep from the launch-host
resolver, fence the live sets with it, and refuse the sleep when rival rows
disagree about the host instead of guessing local. The maps are typed and
named by host-scoped key.
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(native-chat): keep transcript text clear of the message rail
* fix(native-chat): measure the rail's width before first paint so a narrow chat opens without a text shift
* fix: synchronize native chat rail resize updates
* test: give rail navigation viewport a measured width
Settings > Chat > Providers showed Antigravity's sign-in as a filled grey box holding only a
bright primary "Sign in" button. The host learns whether its chats are signed in only when a
chat starts, so after launch the status line had nothing to say and the box sat empty.
The area is now a plain row under the provider's name: "Account" on one line, the status on the
next, and an outline "Sign in" button. Until the host knows, the status line says chats need an
Antigravity sign-in separate from the agy terminal account.
* fix(ssh): two overlapping bridged attaches hand back the viewer they replace
Co-authored-by: Claude <noreply@anthropic.com>
* fix(ssh): the newest bridged attach keeps its viewer even when an older one answers last
Co-authored-by: Claude <noreply@anthropic.com>
* fix(ssh): an older bridged connect still paints its screen when a newer attach keeps the viewer
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
#30863 made the static job fail on unformatted files; 22 files that landed
around it were never run through oxfmt. Formatter output only, plus one
shared paneActive local in NativeChatStructuredSession.tsx so formatting
does not push that file past max-lines.
Co-authored-by: E2E Test <e2e@test.local>
* fix(memory): release consumed stream buffers and abandoned request state
* fix(memory): preserve queued cancellation for shared promise callers
* fix(memory): keep workspace cache cleanup free of browser side effects
* fix(memory): follow renamed workspace owners in cached diff views
* fix(memory): a viewer unmounting after its workspace is removed cannot re-create cached diff entries
Combined-diff view state, scroll offset and scroll anchor now live in one owner-tagged
entry per view key. A mounted viewer claims its key; writes only update claimed entries,
so the late unmount write that follows a workspace removal is a no-op instead of
re-adding orphan scroll entries.
* fix(memory): tighten retention fixes and let evicted diff views heal
- Combined diff: writes go through a per-mount lease. Forgetting a workspace revokes its
leases, so a late unmount write cannot resurrect entries, while an LRU-evicted entry of
a still-mounted viewer is re-created on its next write. Drops the duplicated view scrollTop.
- NDJSON framer: copy a retained tail only when it is a strict slice of its chunk, copy an
unfinished line once, and copy every rejected-line prefix.
- Binary frame reader: single exit path for the end-of-feed leftover copy.
- Relay readiness reuses waitForPromiseWithSignal instead of re-implementing it.
- PTY preconnect input: pass the listener-removing settle into createInput instead of
mutating the input afterwards.
* docs(memory): state the deferSettled ordering guarantee precisely
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
Move the 'recorded tunnel still matches this build' check into
orcad-managed-tunnel-active.ts as activeTunnelMatches(). No behaviour change.
Co-authored-by: E2E Test <e2e@test.local>
* fix(ssh): system SSH forward processes own their listener even under a user ControlMaster
Forward processes ran through the user's ssh_config ControlMaster, so the master held the
listener (it survived the child's kill) and logged sshd's refusals where the managed tunnel's
fallback check never looked. Every forward process now builds its args through one
unmultiplexed builder (-S none); orphan matching tolerates the older argv.
* docs(ssh): say why a forward never reused a live master's login
* docs(ssh): state the forward login trade-off plainly
* test(ssh): keep the real-OpenSSH test's test-double cast on its SAFETY line
* ci: fail the static job on unformatted files
Runs oxfmt --check over the same tree pnpm format writes (~2s), and makes the
pre-commit hook format markdown and YAML so it covers what CI now checks.
* ci: route docs-only and workflow-only diffs through the formatting check
The format check reads the whole tree, so a diff that skipped the static job
could land drift that fails every later PR. Any changed file oxfmt formats now
selects static analysis (not typecheck or units), like the mobile-only route.
The path detector checks out .oxfmtrc.json for its ignores. The hook no longer
fails a commit whose only matches oxfmt ignores (lockfiles).
* fix(ssh): Disconnect closes the managed server tunnels the SSH session carried
The managed tunnels live in their own manager, so the session teardown closed ordinary
forwards and the connection but left a system-SSH tunnel's ssh -L process authenticated and
listening. The teardown now closes every managed tunnel built or building on the target.
* fix(ssh): a session teardown also closes forwards still starting or awaiting their identity check
* fix(ssh): a connection teardown waits for every forward of it to settle, starts and closes under way included
* fix(ssh): bound a teardown's wait on starts, skip edit rollback after one, and never record a tunnel a teardown overtook
* fix(ssh): an edit a Disconnect cancelled leaves the target's saved forwards alone
* fix(terminal): Restart on a failed terminal opens a plain shell instead of Codex
The exit card's Restart passed a null startup, and a Codex account-restart
fallback added in #25485 turned that null into `codex`. Account switches
already name their agent explicitly, so the fallback had no legitimate caller.
* test(terminal): stub the exit-actions controller without Reflect.get
---------
Co-authored-by: E2E Test <e2e@test.local>
* feat(native-chat): `$` opens Codex skills in the composer
Typing `$` in a Codex chat now opens a skills-only list, as Codex's own
apps do. `/` still lists commands and skills for every agent, and other
agents keep `$` as plain text.
* fix(native-chat): `$` skips shell variables and parameters, as Codex does
In Codex chats `$HOME`, `$5`, `$(pwd)`, `${X}` and `$?` stay prose instead of opening the skill list, so Enter sends them; one classifier now serves the trigger, the menu and the insert.
* fix(native-chat): `$` matches skill names only, as Codex does
A `$` query no longer matches skill descriptions, so `echo $CODEX_HOME` + Enter sends instead of picking skill-installer; `/` still searches descriptions.
The tunnel was rebuilt only when this desktop's own check saw the server start. When another
desktop restarted it onto a new port, the old forward stayed and every check answered
unverifiable. A check that finds the server not answering now reads the port the host's
readiness record names and, when it moved, rebuilds the tunnel there.
* perf: preserve matching Terminal settings sections during search
* fix(settings): keep a retained Terminal sessions section fresh and share the section stack
Move the search-stable section wrapper into SettingsSectionStack so every settings
pane keys sections by their own element key instead of the array index, and subscribe
ManageSessionsSection to the daemon session inventory invalidation signal so a kill
routed through another surface still reaches a section that no longer remounts.
Restates the read-count oracles as node identity plus non-growing read counts so a
future freshness fix does not read as a regression.
* test(settings): mock the Windows administrator section in the Terminal section-lifetime suite
* style: format Terminal section-lifetime reliability gate
* perf(push): wake delivery worker after notifications are queued
* perf(push): wake one drain per send so bursts cost O(sends + drains) claims
A wake started all twelve drains, so each send arriving at an idle worker cost
~13 claim transactions. A wake now starts one drain; the poll tops up to the
full drain set.
A remote-runtime automation whose terminal stays open after completion kept
its 24h terminal.wait and data stream alive, holding one of the server's 16
long-wait slots each. Abort the wait and close the stream on dispose.
* fix(session-tabs): a held paired-tab close keeps hiding through an undecided re-pair, and its expiry redraws the tab
* fix(session-tabs): dropping a held close also clears its hide under the current pairing
* fix(session-tabs): redraw only the worktree a dropped close un-hid; inventory frames see the redraw mark
* fix(session-tabs): a re-pair proven the same machine asks for the snapshot its moved closes wait on
* fix(session-tabs): a dropped close clears only the current-pairing hide it laid
* fix(session-tabs): every un-hide of a held close lets its worktree's current snapshot redraw
* fix(sidebar-inputs): a failed domain read is retried by the next flush or phone read
A failed producer read used to clear the domain from the dirty set, so it stayed
stale until its own source announced another change. It now stays dirty without
a new mark: the next flush or phone read retries it, and nothing schedules one.
* docs(sidebar-inputs): flushDirty reads what was dirty; a failed read stays unpublished
* fix(ssh): an SSH server update counts terminals again just before it stops the server
The SSH update passed the terminal census taken before the artifact upload and no recount,
so a terminal opened during the upload was missed and, on a host running terminals inside
orcad, ended by the restart. The recount is now a required deploy option, so every runtime
(SSH, WSL, first deploy) supplies one and the stop step always recounts.
* fix(ssh): a recount that throws reads as unverifiable and defers the update
* fix(renderer): re-activating a workspace keeps the host it was selected on
A failed sleep, an automation's focus restore and a rename event re-activated
the workspace by id alone, which cleared activeWorkspaceExecutionHostId. For a
same-id twin (local + SSH) that left the active workspace ambiguous, so owner
resolution refused terminals and writes or guessed this computer. Each site now
passes back the host captured before the change.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): sleep restore assertions name the restored host argument
* test(automations): type the selected host without an assertion
* fix(renderer): same-workspace re-selection keeps the selected host; restores yield to a newer pick
Link clicks and surface reveals inside the active workspace re-selected it by
id alone, clearing the selected twin's host. They now pass it back through one
getActiveWorkspaceExecutionHostId helper (the owner module's private copy moved
there). The failed-sleep and rename restores skip when the user picked another
workspace during their awaits.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(renderer): one re-select helper keeps a single-argument call when no host is selected
* fix(ipc-events): a rename restore also yields to a twin copy picked during the refresh
* refactor(renderer): capture and restore the workspace selection as one id+host value
Every restore site (failed sleep, automation focus restore, rename) now goes
through captureWorkspaceSelection / isWorkspaceSelected / restoreWorkspaceSelection,
so the host can't be dropped or replayed over a newer pick.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(native-chat): a chat's visuals folder lives as long as the chat
A chat's scratch folder was deleted once the chat had gone 30 days
unused, so an old chat's inline visuals turned into "Visualization
unavailable" while the chat and its history stayed. The common pattern
keeps a chat's files for as long as the chat and never deletes them on
an "unused for N days" timer.
The sweep now removes only folders no chat record maps to, plus
leftover .expired folders older builds parked. Everything that existed
only to make the age rule safe goes with it: the per-folder save lock
and its expiry candidates, the session-lane idle check, the journal
last-activity query, and the migration's fresh-window timestamp. The
visuals skill no longer tells agents the folder is temporary.
* fix(native-chat): an image sent in a local chat lives as long as the chat
A local chat's pasted images lived in one paste folder that deleted
every file older than 30 days, sent or not, so an old chat's images
turned into label chips. Paired-server chats already keep pastes in the
host's attachment store, where the send that names a file claims it for
its chat and only unclaimed files are ever swept: the common pattern.
A local composer paste now goes into this machine's own attachment
store (starting the local chat host first), so its send claims it the
same way. The old paste folder is no longer written or swept: messages
sent before this change may name its files. Restored drafts still read
it.
An unclaimed upload now waits 30 days instead of one before the sweep
takes it. A draft names its image by this path, so the window is how
long an unsent draft keeps its image; 30 days is what local drafts had,
and it outlasts the host's send-id window so a resent draft still finds
its image.
A chat folder an older build set aside as unused (<hash>.expired) goes
back to its chat instead of being deleted.
* fix(native-chat): a terminal-backed chat's pastes are never swept
A terminal-backed chat has no journal, so no send claims its pastes and
nothing can tell a sent one from an abandoned one. Its pastes therefore
stay in the paste folder, which is no longer swept, rather than in the
attachment store, whose sweep would take them after 30 days unclaimed.
Only a local structured chat's paste (forStructuredChat) goes to the
store, where its send claims it.
* fix(native-chat): keep the attachment owner's literal kind for structured local chats
* test(ipc): expect the local chat store option on the clipboard registrar
* fix(native-chat): preserve attachment ownership and restore held files safely
* fix(native-chat): settle restored image checks independently
* test(native-chat): assert restored image outcomes without batching
* Re-check draft images when the attachment sweep removes their upload
A draft checks its pasted images once, when its composer comes back. Every
chat in an open workspace mounts its composer at launch, so that check ran
before the store sweep (60 s after host start, then every 6 h) deleted an
expired unsent upload, and nothing checked again: the chip lost its picture
with no Not kept feedback and Send stayed enabled.
The sweep now reports each removal attempt; the main process forwards it to
its window, and the draft store starts a fresh finite check for every loaded
local draft image in that folder, mounted or not. A notice is a reason to
check, never proof of absence.
* Let the app effect remove the draft re-check listener it adds
startNativeChatDraftLoad dropped the unsubscribe for the upload-removal
listener, so each recovery through the root error boundary's Retry remounted
App and added another listener. Return it so the effect's cleanup removes it.
The drag carried bare worktree ids, and the drop read each id's host from the
id-keyed worktree map, which keeps one row per id. When the same worktree is
published by two hosts (a local and an SSH copy), dragging one card could set
the status or manual order on the other host's copy.
The pointer and native drag sessions now record the dragged cards, and their
visible lineage children, as (id, host) copies; the lineage expansion runs by
host identity so a twin's children are not pulled in. The shared drop-commit
context hands the copies to every status, reorder and board-drop commit.
Every write then resolves through one choke point, dropWriteRows: a dragged id
writes only the dragged copies (read fresh; a copy gone by drop time is
refused), and any other id (a re-ranked sibling, a drop with no drag session)
only when a single host publishes it. The board drop indicator reads the same
rows, so the preview matches the commit.
Setting a project up on another host (a cloned or imported folder)
only knew how to link the folder through a GitHub identity. A project
keyed by its Git remote (GitLab, Bitbucket, a self-hosted host) was
always refused with "Imported folder does not match the selected
project identity". A fresh clone's remote is not probed when it is
registered, so it never projected into that project by itself.
The link now probes an unprobed folder's remote. If the canonical key
of that remote is the project's, the remote is recorded on the repo, so
the folder projects into the project. SSH and HTTPS forms share a key.
A GitHub project is still linked by stamping upstream, and a remote
that names another repository is still refused.
* fix(native-chat): a usage limit reads as a pause with its reset time and a Continue, not a failure
Claude, Codex and Grok chats that hit the account's usage limit now end the
turn on one warning row that names the limit and, in the reader's clock,
when it resets, with Continue to pick the turn back up. Each adapter reads
its protocol's typed limit signal into a `usageLimitReached` failure fact;
older clients keep the host's sentence.
* fix(native-chat): keep the failure words and chat session files within their line budgets
Moves the attachment-problem sentences to their own file and hands the composer's send straight to Continue.
* fix(native-chat): drop a check the shown branch already rules out
* fix(i18n): ship the usage-limit failure words in the runtime catalog
* fix(native-chat): reconcile usage failures by owning turn and typed cause
* fix(codex): keep snapshot import type-only
* fix(claude): open refusal turns without opening empty placeholders
* fix(native-chat): bind usage Continue to the latest idle failed turn
* test(native-chat): isolate stale Continue from ordinary delivery progress
* test(native-chat): use a braced journal assertion
* test(native-chat): build failure rows through their word factory
* fix(claude): retain terminal failure evidence through result reconciliation
* fix(native-chat): complete usage Continue through the composer sender
* test(native-chat): provide the composer options surface in Continue coverage
* test(native-chat): use typed failure words in renderer fixtures
* test(native-chat): match the remote send payload apart from transport options
* fix(native-chat): replace the previous sender notice on Continue
* fix(native-chat): clear superseded Continue notices before rendering
* fix(native-chat): preserve ordinary composer send arguments
* fix(claude): resolve terminal ownership and preserve resultless quota failures
* fix(claude): keep repeated terminal detail unchanged and complete test fixtures
* fix(claude): resolve deferred terminal replays after owning journal writes
* refactor(native-chat): share tombstone append within the sink line budget
* fix(claude): retain both input and echo correlation on the live turn
* fix(claude): preserve unowned result completion after terminal resolution
* fix(claude): allow unassociated successful goal completion
* fix(claude): keep terminal completion on the live turn
* fix(claude): keep typed notices separate from quota failures
* fix(claude): preserve context-limit failures as ordinary errors
* fix(native-chat): keep quota failures visible in collapsed turns
* fix(native-chat): return keyboard Continue focus to the composer
* test(native-chat): keep quota fixture context stable
* test(native-chat): hoist quota context to module scope
* fix(i18n): use one Chinese word for the agent in both Continue tooltips