* fix(mobile): keep a terminal tab's chat identity while its handle is unchanged
A status frame that omits agentType or providerSession flipped chat back to the
terminal for tabs with no launchAgent (#31977), and left a launched tab with no
session to read (#32134). Mobile now remembers the last resolved agent and
session per tab and handle, so a missing field is treated as missing evidence.
* fix(mobile): keep remembered chat read-only and drop it on relaunch
A chat resolved only from this phone's memory may sit on a bare shell, so its
writes stay locked until live status confirms the agent. A live agent frame
without a session no longer inherits the previous conversation, and the
latest remembered agent outranks an older launch hint.
* fix(mobile): gate native-chat image sends on a confirmed live agent
An image send from a chat only this phone remembers could paste into a bare
shell; it now shares the confirmed-agent gate with text sends.
* fix(mobile): type remembered chat identity structurally
Importing MobileSessionTab into the eligibility module pulled React Native
globals into the e2e program through nested-agent-idle-session-owner.spec.ts,
retyping setTimeout as number there.
* fix(native-chat): attach a long paste as a text file, and say a message is too large before it is sent
A paste of 32 KB or more (or one that would push the message past the 256 KB
send limit) into a structured chat is saved as a file the agent reads, through
the same pending-chip and @path route a dropped file takes: Orca's paste folder
here, or the chat's attachment store on its paired server. Claude gets the paste
folder as an allowed directory. A message still over the limit is handed back
before it is sent, on desktop and the phone, with words naming the limit instead
of the generic 'not sent, send it again'.
* test: Claude launch parity snapshot grants the paste folder
* fix(native-chat): a long paste replaces the selection at once and settles in place
A long paste now puts an inline Pasted text pill where it went, replacing any
selected text immediately, and the saved file (or inline text, or the original
selection on failure) replaces that same pill when the save finishes, in its own
chat's draft. Nothing reads the caret later, so order and position hold. The
paste never joins the image row, so image numbers are unchanged. The size check
measures the real message body with the selection replaced and images included.
A server that keeps no files takes the paste inline when the message still fits.
A pending paste a restart lost shows Paste not saved, and Send says so.
* fix(native-chat): type the pasted-text parser and the composer's missing draft scope
* fix(native-chat): admit paste references before goals and commands
* fix(native-chat): resolve staged paste paths without editing the draft
* fix(native-chat): consolidate paste imports for code quality checks
* fix(native-chat): retain checked string types for saved paste paths
* fix(native-chat): retain completed paste facts through history and delimit paths
* fix(native-chat): keep structured question rendering within file limit
* fix(native-chat): pasted or inserted text that looks like a paste token stays literal
* fix(web): keep the browser's UI bridge under the file length limit
* style: format the files #29982 merged unformatted
* fix(native-chat): retain oversized drafts before send admission
* fix(native-chat): keep the composer handle's merge resolution to main's destructure
Preserve deliberate terminal taps after scrolling and keep them ordered with Live input. Retain fresh keyless edits, cap coalesced sends at the existing UTF-8 request limit, and settle cancelled flush barriers without waiting for retired acknowledgements.
Adapted from @innocarpe's community PR #30514, with additional ordering, lifecycle and bounded-send regressions.
Fixes#30510
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
* fix(mobile): retain quick command editor ownership during saves
A deferred Save discards newer edits or closes a replacement editor, including after host/repository changes. Bind each save to its editor and scope; preserve newer text and the persisted command id.
* fix(mobile): preserve quick command saves through suspended renders
* test(mobile): preserve quick-command edits during connection migration
Capture Android terminal keyboard controls in the OTA live input before native web input edits or moves focus. Preserve composition handling, send ordering, and stale-target protection.
Fixes#25496
Co-authored-by: Sangrak Choi <kars@kargn.as>
Co-authored-by: zenghaolun02 <zenghaolun02@baidu.com>
Concurrent close replies filter captured terminal arrays and resurrect an already closed terminal. Reconcile the current collection synchronously and respect newer selection and metadata.
* fix(mobile): invalidate chat file search on authority migration
Modern search caches and late replies survive authority migration on the same client object. Own cache, capability decision and inventory by logical generation.
* test(mobile): record retired file-search requests after reset
* refactor(browser): simplify address bar callbacks within lint limit
* style(browser): keep client-hosted pane within line limit
* fix(i18n): retire obsolete Portuguese default zoom entries
* Use one model catalog per execution scope
* Accept a missing session record in host scope
* Merge duplicate launch-resolution import
* Fix catalog typecheck errors
* Align catalog tests with the one-list design
* Type the integration fixture's send without a cast
* Give the discovery test complete runtime dependencies
* Fix model catalog completion and scoped configured display
* Preserve model catalog authority across released clients
* Fix mobile first-frame catalog fixture narrowing
* Keep startup catalog persistence with one completion owner
* fix: preserve chat selections across CLI model updates
A newer CLI can omit a model that an existing chat still runs. Keep that chat's selected model and effort instead of replacing them from the available list.
Discover one model list per host, account, launch command and folder, refreshing after two minutes. Keep current values and the running model's exact effort choices with its chat. Show only the known effort when Claude supplies no matching menu.
Preserve older paired-client delivery and configured launch arguments. Make Codex use independent discovery and retain the established ACP and Pi paths.
Tracked in STA-10947.
* fix: keep the Pi options read within its file limit after main's growth
Tracked in STA-10947.
* fix: import the option snapshot's shared module once
Tracked in STA-10947.
* fix: keep Grok and Pi model menus and fast controls across restarts
Restore the established ACP and Pi catalog route: newer listed models and qualified startup defaults stay visible, and genuine retained effort menus survive later reads. Keep exact-child Fast and Speed controls. Update the phone option contracts and the remaining stale expectations to the selection rules.
Tracked in STA-10947.
* fix: share each workspace's model list and keep live Grok and Pi menus
New chats read their workspace's own model list through the one shared request for that workspace, so a second chat in the same folder starts on the model and effort the agent actually runs there, and a slow first answer can no longer overwrite a newer list. A running Grok, OpenCode or Pi chat keeps its live model menu when an older shared list arrives later. Update the tests that still expected the replaced behavior.
Tracked in STA-10947.
* fix: keep separate agent and chat model lists for Grok, OpenCode and Pi
A brand-new Claude chat now offers the selected model's effort levels before the first message, as it did before. Grok, OpenCode, omp and Pi again keep the list read from the installed agent apart from the list a running chat reports, and each newer list replaces only its own source. So a chat shows the model the agent is actually running with that model's own effort menu, and an effort change can no longer act on an older model's menu. Saved model lists on WSL are readable again without starting the agent.
Tracked in STA-10947.
* fix: keep desktop catalog storage out of the phone's type graph and show Grok's late model answer
The phone app's type check no longer pulls in desktop-only catalog storage. The model-list code no longer reaches the saved-catalog file through a type import. When a Grok model change times out and Grok later confirms the model, the chat shows the model Grok actually runs.
Tracked in STA-10947.
* Remove unrelated merge formatter and package manager churn
* Clarify independent catalog ownership and refresh contracts
* Declare picker demand callback as an effect dependency
* Complete mobile catalog surface and catalog merge types
* Complete catalog contract fixtures for current host types
* fix: preserve catalog source contracts and account fallback
* test: inject independent Codex discovery transport
* test: remove accidental client fixture assertion
* test: qualify the saved account fallback read
* fix(chat): preserve catalog evidence and current child controls
* Fix catalog CI types and mobile option readiness
* Reconcile session controls after inventory changes
* Fix catalog import and deferred test resolver types
* Keep model capability observations separate from picker views
* Use installed model choices for chat options
* Fix model catalog test fixture contracts
* Restore model option compatibility and stable settlement
* Return a catalog reply in provisional launch fixture
* Compose unreported live model defaults in the host
* Preserve explicit replacement model on recovery send
* fix(chat): use stable scoped catalogs on local desktop
* test(chat): preserve generic catalog runner type
* fix: scope catalogs to configured environment and keep saved reads passive
* fix: satisfy catalog regression and controller CI checks
A reused file reader carries a note composer into another document, and a late submission closes the new composer. Give document state and callbacks a keyed lifetime.
The phone and the desktop each parsed the status row's
{ approval: { tool, summary } } envelope with identical validation.
Move that validation and the answer keys into
src/shared/native-chat-approval-envelope.ts; each caller still builds
its own card (phone English labels, desktop translated labels) and
keeps its own precedence and heuristics. No behaviour change.
Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
Use the shared desktop image-path formatter for unidentified mobile terminals and identified Codex, preserving owned agent identity, bracketed paste, upload routing and acknowledged-input ordering.
Adapted from community PR #30310 by Wooseong Kim (@innocarpe). Fixes#30298.
Validation includes exact-head CI and aggregate gate, native macOS and Windows consumer checks, actual paired Windows uploads with byte-integrity checks, and combined mobile/desktop tests, types, lint and bundle checks.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Preserve terminal reading position on foreground and reconnect by reusing the existing scroll-preservation option. Adapted from community PR #23401 by Shahar Mor (@shaharmor), with native iOS before/after proof and 38 focused regression tests.
Co-authored-by: Shahar Mor <shaharmor1@gmail.com>
Retire queued continuation echoes when transcript rows represent the sends. Preserve literal backslashes, occurrence boundaries, and image barriers on mobile and desktop.
Closes#15288.
Adapted from #15283.
Co-authored-by: grboy6770 <2000grboy@gmail.com>
Preserve Cursor identity across imported Claude hooks and mobile terminal tabs while retaining current tab status, rename, and scroll behavior.
Closes#30543.
Adapted from #30544.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Dispatch the leading terminal scroll immediately and coalesce subsequent movement behind the outstanding request. Preserve queue expiry, limits and ordering.
Closes#30299.
Adapted from #30311.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
* Mobile chat composer uses desktop's placeholder
The phone composer said "Message Codex" (or "Message, @files, /commands") while desktop says "Ask anything, @ to mention files, / for commands". Both now read the one string from the shared native-chat copy table, so they can't drift again.
* Ship the shared composer placeholder in the runtime English catalog
* Keep the composer placeholder beside, not inside, the turn-status copy
The turn-status table maps one-to-one onto the status.* catalog keys; the placeholder is composer.placeholder, so it gets its own constant and its own catalog check.
* Read the composer catalog without a type assertion
* Read the composer placeholder from the typed catalog directly
* fix(terminal): start every terminal in the workspace root when its saved folder is gone, and say so
A terminal whose start folder was deleted opened at the workspace root only
when the window started it; the CLI, phone restore, orchestration and other
host starts failed with the raw "Working directory ... does not exist" error,
because the renderer had to opt in with a cwdFallback flag.
The execution host now makes that decision for every start that may create a
process (anything but a known live pane), with no caller flag. The fallback is
reported only when a process was proven started at the requested folder, so a
reattach or a crash restore in its recovered folder never claims "workspace
root", and only then does the saved start folder heal. A recovered folder that
was itself deleted yields to the request's resolved folder.
The notice reaches each caller through surfaces it already has: the window's
terminal line (now also written when a host-created tab attaches, and on a
restored reattach), the agent.launch warning (also for callers that show the
tab themselves), and a toast when a phone tap restores the tab.
* fix: recover terminal startup folders without healing unreadable paths
* fix: preserve saved start folder when revealing fallback terminals
* Reuse saved-tab lookup in cwd persistence test
* Import mobile tab activation result from its contract
* Check startup fallback response without a test cast
* Update expectation to catch up with accepted saved-folder metadata
* Preserve inconclusive WSL directory evidence when saving startup folders
* fix: project restored terminal ids for handle consumers
* test: return void from launch parity cleanup
* test: retain saved folder in shared spawn expectation
* fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip)
The fixes found while the sidebar ran from main's feed, without turning
that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's
stop-gaps come along.
- Main stops counting a closed tab's terminal once it was asked to stop
and no pane shows it; a failed or refused stop takes the request back,
and a host out of contact stays unverifiable.
- Main fetches a paired server's rows with the transport's normal bound,
so a server slower than 5 s still lists.
- A deferred unread raise flips the folder row it was decided for, and a
read while the owner check is out cancels it.
- Phone: no worktree.ps flash before the feed; a host's last installed
feed stands in while a fresh read is out; it highlights its own
last-opened workspace and a fold never scrolls; projects file under
their project group with the desktop's indent; the collapse spinner
shows only after 500 ms; "Reopen to update" keeps the rows on screen.
- e2e helpers and journeys for the switch-on suite.
* fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises
- A stop-and-wait that throws takes its stop request back, like one that fails.
- A final stop the legacy relay still owes stays marked, since the relay retries it.
- Pending unread raises are tracked only while one waits, so the map no longer grows.
- The paired-server fallback poll test gives its third fetch a reply and asserts live.
Retire old terminal stream callbacks and delayed fits across reconnects, unsubscribe, terminal replacement, and synchronous termination. Preserve fresh output and the latest accepted fit.
Fixes#30505
Adapted from community PR #30506.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
* fix(mobile): remove the chat's Tools toggle and its open-everything mode
* test(mobile): keep the scroll-ownership tests' runs drawn without the Tools default
* test(mobile): the scroll-ownership harness opens settled turns as the old Tools default did
Honor enabled voice settings across terminal/chat controls and capture. Refresh hold/toggle state on connection, focus and foreground while fencing stale replies.
Adversarial review: 63 existing tests plus six disable/late-finish/unmount/settings ownership probes pass; final required CI and aggregate gate pass. Existing native screenshots and microphone lifecycle exercise are attached to the PR.
Fixes#30307. Fixes#30511.
Adapted from community #30316 and #30513 by @innocarpe.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
fix(mobile): keep keyboard accessory row height stable
Stretch the dismiss control within the accessory row so showing it no
longer adds height and needlessly reduces the terminal viewport.
Adapted from community PR #23511 by @innocarpe.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The served session page (react-native-web) reads flex: 0 as flex-basis 0%,
so the title block collapsed to zero width beside the tab strip. flex: -1
is natural width capped at 160, shrinking only when the row overflows, on
both web and native.
* fix(orcad): voice dictation on headless orca serve hosts
Mobile dictation against an orca serve host answered speech_unavailable
since serve moved to orcad: orcad never installed the speech service
factories, and the model manager and download imported Electron.
The model download now takes a host request opener (Chromium net on the
desktop, Node https on orcad, with redirects mapped to the same event),
the STT worker ships in the orcad slot, and orca serve hands orcad the
installed app's on-device recognizer. A host without a recognizer (an
SSH or WSL managed slot) lists only cloud models and sends a phone that
picked an on-device model to the setup sheet. Mobile names the fix when
an older server still has no speech at all.
The parity test now requires both roots to install speech.
Reported-by: delorean2020 (@delorean2020)
* fix(orcad): retry Node download drops, stage the recognizer on Windows, review fixes
- Node https errors (ECONNRESET, ETIMEDOUT, ...) are retryable, so an
interrupted orcad model download resumes (Codex P2).
- On Windows orca serve hands orcad a per-version copy of the recognizer
in the profile, so a loaded addon never locks the app's files against
an update (Opus).
- Drop a recognizer candidate path that could never match; one SAFETY
helper per test file instead of repeated casts; comments.
* fix(orcad): cloud speech is ready only with a key this host can decrypt; prune superseded recognizer copies
Codex round 2: a desktop-sealed OpenAI key exists on disk but orcad's
secret store cannot open it, so the catalog reported cloud models ready
and dictation failed at the end. orcad's model manager now proves the
key decrypts. The desktop keeps its existence check (no keychain prompt).
* fix(orcad): never prune staged recognizers before the profile lock; copy linked packages as real files
Codex round 3. ref-oss check: versioned copies are cleaned only by the
process that owns them, so the pre-lock prune is dropped rather than
patched.
* fix(orcad): remove a partial recognizer copy when staging fails (Codex round 4)
* fix(speech): never hand out another store's ciphertext as the OpenAI key (Codex round 5)
* refactor(orcad): no recognizer copy on Windows; Windows orcad keeps cloud dictation only
Design check after review round 5: every remaining finding came from the
Windows copy that dodges addon file locks. Dropping it shrinks the
surface to a path lookup; Linux and macOS serve keep on-device models.
* fix(speech): name the fix when an old server refuses a mic tap; always set the recognizer env; retry ENOTFOUND (final Opus review)
* fix(managed-server): probe install completeness relative to the slot dir
CI: shipping stt-worker.js added one more absolute slot path to the
install-complete probe, pushing it further past the exec-size budget.
Testing each artifact relative to the slot (inside a subshell cd) cuts
the probe from ~12.2 KB to ~3.6 KB, so it drops off the over-budget
list instead of raising a ceiling.
* fix(mobile): pin host.platform's read to a platform type, independent of which zod copy resolves
With mobile's own dependencies installed (which CI does for any PR that
touches mobile/), the e2e program resolves two zod copies, and the
salvaged platform arm from src/shared inferred as {}. The schema now
narrows through an explicit platform guard.
* fix(mobile): keep the host.platform read line intact for its RPC mutant; narrow in a second step
Mobile verify: the recorded-RPC mutant anchors on the read line, which
the previous guard rewrote, so the mutant could not apply.
* refactor(cli): app-executable resolution in its own module; drop the platform guard now zod is aligned
launch.ts went one line past max-lines with main; resolveAppRoot,
resolveForegroundOrcaExecutable, getExecutableAppArgs and
stripElectronRunAsNode move to orca-app-executable.ts (re-exported, so
callers are unchanged). With mobile and root on one zod (#31260), the
host.platform schema no longer needs the extra narrowing step.
* refactor(cli): import app-executable helpers from their module; no re-exports from launch.ts
Persist unsent buffered terminal commands per paired host, workspace, execution host, and surface across navigation. Preserve newer typing during storage hydration and clear successfully sent drafts.
Validation: draft/storage/bridge tests, signed iPhone simulator build, and all CI checks passed.
* refactor(protocol): each area owns its published runtime capability list
RUNTIME_CAPABILITIES now spreads one array per area instead of a flat
hand-appended list, and runtime-capability-constants.ts is split into
the areas that own each constant. The published set is unchanged and
pinned by an exact sorted test.
* test: preserve capability baseline while allowing additions
* feat(native-chat): Codex chats list and run the skills Codex loaded
A Codex chat's skill list now comes from Codex itself (`skills/list`), live
while the chat runs and refreshed when Codex says its skills changed, and
from a short-lived Codex check while the chat is at rest. Plugin skills keep
Codex's `plugin:skill` names, disabled skills drop out, and each skill file
is its own row. A skill picked from the list reaches Codex as that exact
skill, beside the unchanged `$name` text, on hosts that take it.
* test(native-chat): account for Codex's skill list call; send without a trailing argument
* fix: align skill probes with chat launches and preserve picked mentions
* fix: distinguish skill choices in phone accessibility labels
* fix: clear picked skill files when queued text changes
* fix(native-chat): refresh resting commands when launch settings change
* fix: keep WSL agent refresh on its execution host
* fix: preserve skill selection at read and send boundaries
* fix: narrow agent identity for selected skill notice
* fix: use checked agent predicate for skill notice label
* test: retain locale in composer translation mocks
* fix(native-chat): a chat's model, effort and Fast belong to the chat, not to a model
Structured chats kept their option values per model on the client, so a value
the host saved without a model (a resumed chat that never picked one, Codex
Fast saved on the conversation) had nowhere to live. The client either dropped
it or labelled the chat with the listing's default model to hold it.
- One flat record per chat holds model, effort, speed and Fast independently;
a model pick changes only the model and the host reconciles the rest.
- Controls come from the model they apply to, separately from the values; with
no model known, a saved Codex speed still shows and Standard turns it off.
- The options answer marks values a catalog default supplied
(current.defaulted). A resumed chat never names or sends such a model as its
own; a new chat still shows the default the host says it runs. An answer
without the mark reads as before.
- Every send, including a new chat's first message, carries the chat's own
model and effort and held picks, never a default read off the picker.
- A held launch pick supersedes only an earlier pick of the same option.
Tracked in STA-10947.
* fix(native-chat): a new chat's first send still pins the default model it shows
A new chat that never picked a model carries the default its picker names, as
before, so it keeps the model it actually ran after the CLI's default changes.
Only a resumed chat's defaulted model is never named or sent.
Tracked in STA-10947.
* fix(native-chat): narrow the host catalog before reading whether its listing names the default
The flag lives only on a listed catalog, so the check confirms the catalog is not the unknown
answer before reading it; the first-frame test reads the chat's flat model value.
* fix(native-chat): a reopened chat whose saved model was refused records the model it now runs
After a retired saved model is reset, the options answer no longer marks the model the child runs
as a default, so the next send records it instead of replaying the refused model on every reopen.
* fix(native-chat): a new chat names and pins the default it shows, and a model pick keeps only what the model offers
A chat this view started names the default model the host fills in, whichever of the model list and
the options answer lands first, and its first send carries the shown model and effort. A resumed
chat still names and sends only its own values. A model pick, held or live, keeps an effort, speed
or Fast only where the new model offers it, so a value the model cannot take is never shown, sent
or remembered.
* test(native-chat): give the no-Fast model fixture its required default flag
* fix(native-chat): an open chat's model pick leaves reconciling to the host
A live pick no longer drops values on the client; the host's reply, when it carries its full
value set, replaces the chat's own values as a read does. Only a pick held before launch, with no
host yet, keeps just what the picked model offers.
* fix(native-chat): a pick reply only adds the values it names
A pick's reply carries only the values the host set or was asked for, so it can't speak for the
rest: it no longer removes values it leaves out. Joined tests pin real reply shapes and a new
Claude chat in a fresh Claude home naming and pinning Opus High.
* fix(native-chat): a tier the drawn model doesn't offer shows as Standard
A Speed value another listed model offers but the named model doesn't is drawn as Standard, which
is what the host reconciles it to, while the chat's record keeps the host's value. Effort, a tier
no listed model offers, an unlisted model and a stand-in model's controls still show what the host
holds. After a lost pick reply the picker no longer shows the old tier as a raw extra choice.
* fix(native-chat): a never-sent chat names and pins its default after a reopen
The options answer no longer marks the default model and effort as stand-ins for a chat this host
started that has sent nothing, so a chat reopened after a restart or on the phone names them and
its first send pins them, as before. A chat taken from history, or one that has sent, keeps the mark.
* fix(native-chat): narrow the missing record as null and type the new-chat fixture's handle chain
* test(native-chat): the unloaded-turn context test's record store answers null for a missing record
* fix(native-chat): a chat's speed shows what its next turn runs
A Codex tier the drawn model lacks shows as Standard, which is how Codex runs it, except Flex, which
Codex passes regardless: established Flex keeps showing, and after a model pick the host hasn't
answered it shows no selection until the reply or a read. Each Codex turn sends the same effective
tier without rewriting the chat's saved one, and a new chat's create carries held picks as the
picker draws them, settling them so they aren't applied again after publish.
* fix(native-chat): one speed rule for picker, host and turns; never-sent needs proof; create keeps held picks
A Codex tier the model doesn't list shows and runs as Standard under one rule shared by the picker,
the host's live reconciliation, each turn and the at-rest answer, which keeps the saved tier for a
model that offers it. An older host's Flex shows as it reports it. A chat counts as never sent only
when no turn, rewind or untrustworthy journal says otherwise. A new chat's create carries held picks
as the picker draws them but keeps them as the user made them until create succeeds, so a refused
create and retry still has the user's own pick, and a newer pick still applies after publish.
* test(native-chat): type the resting journal mock and cut overlapping option tests
One typed resting-journal helper replaces six inline mocks and the broad object parameters the
low-evidence check rejects. Cuts tests the joined flows already pin: the resumed list-or-answer
ordering test, the restart-after-first-send view test, the held effort before a model pick, and
the model-pick and restart loop of the saved-Fast test.
* test(native-chat): cut option assertions the joined flows already pin
Drops the Codex Standard row (the joined tier test pins it), the dispatched-source and literal
effort-choice checks, and the resumed view test restart and model-pick steps; folds the pick table.
* fix(native-chat): an at-rest pick reply and answer name the speed the next start runs
The reply to a pick made at rest now carries the tier the chat model runs, through the same rule as
the at-rest answer, so an older app whose follow-up read fails shows what the next turn sends; the
saved record keeps the pick. A saved tier no listed model offers (a Flex from the user Codex config)
is the agent configured tier at rest too, as the live rule and the picker already read it. Restores
the pin that an accepted Codex reply naming Standard shows with no later read.
* fix(native-chat): a reopened chat with no saved model names its default at once
A reopened chat that never saved a model showed just "Model" until a send started its agent, while
main names the list default straight away. The picker now names the default the host marks for every
chat, as main does. Whether a send carries that default is a separate rule: a new chat sends it, so
its first send pins it; a reopened chat does not, so its next start still runs the agent's own.
* fix(native-chat): a chat with no saved model keeps the model its first turn ran
A chat that never saved a model followed whatever the agent's default was at each start, so a chat
resumed from history or a terminal could quietly switch models after a restart. When a message is
handed to the agent on such a chat, the host now saves the model that turn ran: the message's own
model when it carried one, otherwise the model the agent reports running. A default the host filled
in, no model at all, or a model the agent would refuse as a choice on later sends, is never saved. The write sets the model only while none is saved, inside
the record store's transaction, so a pick made meanwhile always wins and effort, speed and Fast are
untouched. One host step covers Claude, Codex, ACP agents and Pi. No new saved field.
* fix(native-chat): a chat with no saved choices keeps the model and effort its first turn ran
A Codex message that left the model or effort unset was filled from the model list's defaults, not
from what the running thread uses. Once the host saved the model a chat ran, the next Codex turn
carried that model alone and ran the list's default effort for it (a configured xhigh became
medium), and a message carrying only an effort ran the list's default model while the chat saved
the thread's. A Codex message now resolves an unset model or effort to the thread's own, and only a
switch to a different model takes that model's default effort, the same rule the other agents use.
When a chat with no saved model records what its first turn ran, it now records the effort, service
tier and Fast that turn ran too, in the same one-time store write: each only where the chat has none
saved, only from the message's own value or the agent's real report, never a filled-in default, so
a later change to the agent's configured effort no longer pairs the kept model with an effort it
never ran. No new saved field and no wire change.
A Codex follow-up sent while a chat's first turn runs is compared with what that turn actually runs,
so it steers instead of waiting for a new turn just because the host saved the model meanwhile.
Also fixes the delivery loop's optional recording hook type.
* fix(native-chat): key what a child ran by its provider child type
* fix(native-chat): record each option a turn ran where none is saved, and drop what a model pick can't run
A Codex message that only switched the tier set the thread's effort as a lasting override, so a later
switch to a model with no listed efforts was refused; the effort is again written only where the
model brought one or an earlier override needs replacing.
Recording what a turn ran now works per option: a chat that already saved its model still records
the effort it ran, and so on for each option it has none saved for. Tier and Fast come only from the
agent's own report, since a message steered into a running turn runs that turn's, and Fast is kept
only when the agent reports it actually routed on: an off state may only mean Fast is unavailable.
A model pick drops a saved effort, or Fast, that the newly picked model's listed row does not offer,
in the one host step both resting and live picks go through, so no later send carries a choice the
agent would refuse. The saved tier is kept, as before: it runs as the model's effective tier.
* fix(native-chat): save only options a turn really ran, and only ones the chat's model runs
A chat with a saved model recorded the effort its agent's settings named, not the one the turn
ran: with "effortLevel": "high" in Claude's settings and Haiku saved (no effort control), the chat
saved high and every later send was refused. A Codex thread effort its saved model doesn't list
did the same.
What a turn ran now comes from the agent's own dispatch: Claude reports the choices it applied and
the effort the CLI resolved for the model it applies, never the settings level alone; Codex reports
its final request over the thread's own model and effort, and a steer reports its running turn.
This evidence lives only in memory on the dispatch result; agents without it keep using their own
reports.
One admission rule now decides every saved option, inside the ordered record write: under a model
the saved catalog lists, an effort must be one it offers and Fast must be one it replays (Claude
needs positive support). A late record of a turn that ran on another model than the one the chat
now has saves none of that turn's options. A model pick drops inherited options its new model
doesn't run, an explicit pick or a new chat's option the model doesn't offer is refused with the
existing option refusal, and unknown catalogs or unlisted models pass as before. The saved tier
keeps its effective-tier rule, and nothing already saved is cleaned up later.
* fix(native-chat): an attach that founds a chat admits its options against the saved catalog too
* fix(native-chat): Claude and Codex always name what a turn ran, and picks are judged inside the write
The handover type was misnamed, which broke the typecheck. Claude and Codex now attach their
dispatch evidence even when it names nothing, so a Claude turn dispatched before its settings
readback never records the settings effort it did not run; the agent's own report still names the
model and stands in only for agents without dispatch evidence. Recording leaves the saved-model
and set-if-unset checks to the store's one admission rule. An explicit effort or Fast pick is now
checked inside the ordered write, with the same refusal, and only at rest: a running chat's own
menu can offer more than its saved catalog row (Pi's probe menu stops at high).
* test(native-chat): Codex admission tests accept the dispatch's run evidence
* fix(native-chat): a new chat's options are admitted against the folder list its picker showed
* fix(native-chat): a new chat's admission reads the list its picker read, resolved once by the create
* chore: re-run CI against main's formatting fix (#30918)
* chore: re-run CI against main's formatting fix (#30948)
* test(native-chat): explain the seed-options stub host cast
* fix(native-chat): attach keeps its two-argument call when there are no attach options
* Revert "fix(native-chat): attach keeps its two-argument call when there are no attach options"
This reverts commit e633d2065b.
* test(native-chat): create-path attach assertions check the params argument, not the arity
* fix(mobile): keep the chat composer's controls inside its row
The model pill never shrank (React Native items default to flexShrink 0), so a
long model name slid under Mic and Send. The permission pill now shows only its
mode icon, resolved from one shared mode->icon-name table that desktop uses too.
* fix(mobile): resolve the permission icon only when a pill renders
* style: format the permission pill test
* fix(desktop): keep the permission icons a static record
* fix(mobile): reopen the phone chat's live feed from its last row when it ends
The phone opened a chat's live transcript stream once per connection and never reopened it when the
stream ended or failed while the connection stayed up, so the chat silently froze on old rows. Move
desktop's reopen-from-cursor read into src/shared and run the phone on it too, with an app-foreground
retry.
* fix(mobile): retry the chat feed on the app-resume signal client-context already relays
Module mocking react-native in the shared vitest setup is rejected; publish the existing revival
nudge instead, the way desktop's host-contact signal feeds its read.
* test(mobile): let the phone's coalesced live read apply each frame before comparing
* fix(native-chat): save every chat reply and tool output whole; bound only the copy sent to clients
Orca cut Codex replies, every agent's reasoning, plans, prompts and all tool
output to a 16 KB head when it saved them, and threw the rest away. The
limit existed so a reconnecting client is not replayed a huge row, but it
was applied at save time, so every transport limit became permanent data
loss.
The journal now keeps what the agent said. One outbound form, applied to
every history page, live batch and catch-up, sends a client the same 16 KB
head of each tool output and diff it always got, and marks the item
`shortened`. A new capability-gated `agentSession.item` RPC returns the
whole item, and the desktop and phone show "Show full output" / "Show full
message" when the copy was cut. Items past half a page are shortened and
marked instead of replaced with "too large to show".
Two bounds stay, both named: a 4 MiB ceiling per text (head and tail),
applied where every provider's rows enter the write queue so one write can
never stall a chat, and a preview on Grok/OpenCode/OMP/Pi calls while they
run, whose closing row is built from the whole value the translator held.
* test(native-chat): an over-budget history item is shortened and marked, not replaced
* fix(native-chat): format, keep the sink and projection under the line limit, update method count and tests
* fix(chat): preserve complete writes and keep detail current
* test(chat): align final tail reservation with captured content
* Prototype source-batch journal ownership for Claude
* Pin the resolved iterator promise in ownership experiment
* Revert "Pin the resolved iterator promise in ownership experiment"
This reverts commit bdb49ab738.
* Revert "Prototype source-batch journal ownership for Claude"
This reverts commit e5ed387f0c.
* Preserve large received chat content while pacing input
* Verify received chat writes wait for journal persistence
* Close test journal databases before removing temporary roots
* Exercise item detail in the cross-version chat wire manifest
* Pin complete saved output and fitted client publication
* Exercise Pi exit retention with real journal write pressure
* Settle terminal web test unmount before DOM teardown
* Reuse existing readers and contract support in session tests
* Reuse measured chat item sizes in history pages
* fix(chat): derive page form type from reader contract
* fix(chat): open phone user messages while loading full text
* test(native-chat): reuse imperative context provider construction
* test(native-chat): format full-content disclosure fixture
* ci: preserve unit shard diagnostics after a timeout
* ci: retain unit shard output when tests time out
* ci: expose unit shard watchdog and output stages
* Revert "ci: expose unit shard watchdog and output stages"
This reverts commit 0b4991b144.
* Revert "ci: retain unit shard output when tests time out"
This reverts commit 87eec4c04f.
* Revert "ci: preserve unit shard diagnostics after a timeout"
This reverts commit 1888c656bb.
* test: verify complete Claude tool output without large failure diffs
* Simplify reply extraction in cross-version session test
* Restore full message recovery for grouped phone thoughts
* Target recovery button in grouped thought fixture
* test(native-chat): count agentSession.item alongside main's new structured method
* fix(chat): reuse strict session subscription schema
* test(chat): open Codex before checking received content