* test: remove redundant cases and repair behavioral assertions
* docs: reconcile audit records with current main
* fix(deps): apply security updates from #32101
Apply the six-file DOMPurify 3.4.17 and Next.js 16.4.1 patch from PR #32101 to resolve the dependency security gate for this test audit. Original patch by Brennan Benson.
* test: satisfy changed-line quality gates
Add required braces and replace filter indexing with find. Rerun both CI quality gates and the 27 affected relay tests; refresh audit hashes and record the corrections.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* Use incremental tsrs for local typechecks
* Update pinned Bun to 1.4.4
* fix(deps): bump dompurify to 3.4.17 and docs next to 16.4.1 for security advisories
dompurify 3.4.17 patches GHSA-cg2g-xh64-v4q7, GHSA-mrv2-3pqr-j5jr and
GHSA-c6gr-qvv4-5v3f in the root and mobile lockfiles. next 16.4.1 patches
GHSA-2464-fhr4-w9xw, GHSA-673q-mjm6-r5xr and GHSA-p3m5-59mp-jv5r in the
docs site.
* Prepare mobile bundle for smoke benchmarks
* Give native compiler benchmarks a larger ARM runner
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* 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(mobile): the shell returns to a screen already on its stack
The page's own "back to hosts" chevron calls dismissTo('/') inside the page, which the page's
router hands to the shell as one navigate message. The shell pushed every such href, so leaving
a host from its page left [Home, Host, Home] with the old host page still mounted (WebView and
live subscriptions), and every round trip stacked another one. System Back then walked back
through stale host pages.
The shell now reads the navigation container's state first: an href that is already a screen
below this one (same path and query) is popped back to, and an href that is this screen is
dropped. Anything else is pushed as before. The pop names its navigator and source route
explicitly, because a screen's dispatch otherwise stamps its own key as the source and a parent
stack that cannot find it drops the pop.
The wide host-route dismissTo stays for the hrefs this rule does not cover: a host route whose
query differs from the one below (the missing-worktree bounce), and a cold deep link with no host
route below to return to. Overlay replace is unchanged, and so is the bridge message.
* fix(mobile): a param cleared to empty and a fragment still match a screen on the stack
The session screen clears paneKey with setParams({ paneKey: '' }), which leaves ?paneKey= on the
stack entry, so a later navigate to the plain session href found no match and pushed a duplicate.
Empty query values are now dropped when canonicalising, and the fragment is stripped since
expo-router keeps it as a param no href spells the same way.
* feat(mobile): one download bar whose fill follows progress on the native driver
The page download's replies keep the JS thread busy, so React committed
nothing until the end and both bars sat at zero, then jumped to full.
The session now publishes each progress step to a small feed as it is
dispatched, and one shared bar eases its fill to each value with
Animated on the native driver (scaleX). The caption stays React-drawn.
* fix(mobile): the download bar always moves on the native driver
With reduced motion, the fill jumped through a JS-side setValue on a value
that never became native, so on Fabric it only moved with React commits,
which the download holds off. An unread preference also counted as
reduced, so a freshly mounted bar took that path. Every step is now a
native-driven timing, of zero length when motion is reduced, and the
preference is read through a ref.
* fix(mobile): the download bar starts at most one ease per 50 ms
The feed fires per asset, often well under a millisecond apart. Starting a
new 150 ms timing on each one replaced every ease before it drew a frame,
so on iOS the fill stayed near empty until the last update.
Now a callback inside the 50 ms window only records the latest fraction;
the next callback past the window, or a trailing timer at its end, eases
to it over 100 ms. Full lands at once. The effect cleanup clears the timer.
* feat(mobile): download only the page assets the cached generation lacks
Before paging the bundle, the phone matches the new manifest against the
host's active generation by content (sha256 and length). Matches are read
from disk and hashed again while the changed assets come over the wire;
one whose bytes no longer hash to the manifest is downloaded instead.
Progress counts only the bytes that cross the wire.
* test(mobile): put the older-build manifest note back on its describe
* perf(mobile): copy unchanged page assets natively instead of reading and hashing them
The reuse pass read every unchanged asset into JS and hashed it again,
which kept the JS thread busy for most of an update. The cached
generation was hash-verified when it was written, is never written
again, and is what the page already opens as is, so matching assets are
now copied natively into the staging tree and checked by size. A copy
that fails or comes up short is downloaded instead; stage refuses a
reused copy that is no longer whole. Path rules move to
generation-asset-path.ts and the copying to generation-reuse.ts.
* fix(mobile): copy reused page assets at stage, on the store's queue
Copies made when the manifest was read sat in tmp for the whole download,
where a second shell mount's sweep, or a second download of the same
build, could take them away and fail the stage. The fetch now only checks
that each reusable file is still whole (a native stat) and leaves it to
the stage, which copies it from the generation active at that moment and
checks its size. Copy destinations must not exist, as on both platforms.
* feat(mobile): decode page-bundle download windows natively
Each 384 KiB window of the page-bundle download cost about 200-300 ms of
Hermes JS time (tweetnacl secretbox open, fflate gunzip, a base64 re-encode
check and the TextDecoder polyfill), which kept the JS thread busy for the
whole download.
The phone now opens v2 frames with react-native-libsodium (secretbox open and
strict base64) and decodes ranges with a local Expo module,
orca-bundle-window-codec (bounded gunzip via system zlib on iOS and
java.util.zip.Inflater on Android, plus strict UTF-8). The shared v2 session
takes these as optional primitives; the desktop keeps tweetnacl.
If any native piece is missing or fails a known-answer probe, the phone keeps
today's JS path, with the base64 re-encode replaced by an O(1) canonical check.
* refactor(mobile): name the native decode for every inbound frame, not just bundle windows
The native secretbox, base64 and UTF-8 primitives decode every inbound v2
frame on the phone, so the files and the Expo module are renamed for what
they cover (paired-e2ee-native-*, orca-gzip-utf8-codec). A throw from the
native UTF-8 decode now falls back to TextDecoder, since the frame is
already open and its counter advanced.
* test(mobile): pin the TextDecoder fallback when native UTF-8 throws
* fix(mobile): hand the native decode to the transport from the app entry
The transport modules are reached by shared, desktop and e2e code. Importing
the native bindings there pulled expo-modules-core into the e2e typecheck,
whose global types make NODE_ENV required and broke ~20 e2e files. The app
entry now provides the bindings; without them the transport runs the JS path.
* fix(mobile): a native-codec lookup before the app provides bindings does not pin the JS path
* 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): retire dismissed branch diff requests
A pending branch diff reopens a dismissed drawer and blocks replacement previews. Retire requests on dismissal and host/workspace changes, and keep spinner ownership with the current request.
* fix(mobile): retain branch preview through suspended transitions
* test(mobile): retain confirmed branch previews across connection cutover
* 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
Reconnect refreshes only the root and retains deleted nested entries; interrupted children can remain loading. Invalidate child reads and refresh expanded folders. Bound deep indentation to keep filenames reachable.
Fresh repository objects reset explicit setup choices, while host changes can retain stale scripts and trust. Use logical repository and runtime ownership.
Delayed hydration and out-of-order storage writes can reverse a browser destination selection. Order durable writes and preserve the confirmed value when saving fails.
Classify substantially delayed connect/handshake deadlines as interrupted
instead of blaming endpoint reachability, retaining the existing close/retry
path and ordinary timeout errors. Limit sleep-clock compensation to observed
background segments and let fresh failures replace delayed evidence.
Remove the stale host-version fallback and discard captured versions when
report hydration crosses an observed reconnect. Independently reviewed;
128 focused tests, adjacent transport contracts and changed quality pass.
Adapted from community PR #24425.
Fixes#18829.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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>
Switching desktops retains another host history and can scan its filesystem path on the new host. Reset content by host while preserving same-host reconnect identity.
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): own commit generation by workspace and admit once
AI commit generation crosses workspace transitions and admits duplicate calls before a render. Retire the active run on scope changes and use synchronous admission.
* fix(mobile): retire commit generations only after scope commits
Pass the selected repository to New Workspace agent detection through the host-authoritative runtime resolver, including WSL paths and Windows project preferences. Preserve old-client request shape, SSH routing, and strict ownership/refusal behavior.
This merge credits @welldoingk’s original WSL agent-detection investigation and community PR #20101, adapted to the current host-resolved contract.
Co-authored-by: welldoingk <thkwon96@gmail.com>
Fixes#20060
* fix(mobile): show commit comparison failures with retry
Comparison refusals, transport errors and malformed payloads appear as No file changes. Decode accepted data, preserve loaded files on failed refresh, and offer Retry.
* test(mobile): record commit comparison errors and retry affordance
* 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
* 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