Repository navigation
Conversation
…ailure (#26940) ssh2 can deliver the exec ack and the exit status in one chunk, so 'exit' fires before the browse handler subscribes. Fall back to the status that 'close' repeats when no exit was observed. Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: suharvest <suharvest@users.noreply.github.com>
…or the phone (#26442) * feat(mobile): show SSH host health on the workspace row host badge Rows on an SSH host the desktop cannot reach looked healthy on the phone. The badge now adds the desktop sidebar's own word ("devbox · Disconnected"), derived by the same shared function from the connection status ssh.listTargetSummaries already sends; old desktops keep today's badge. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): name an unhealthy SSH host even in a single-host list The desktop card shows SSH status on any host count, so the phone now reads target summaries whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one target-to-host keying, and a hook-level test pins the reply-to-row wiring. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): clear stale SSH host badges and announce them without the dot A catalog that no longer spans hosts or holds an SSH repo now clears the health and label maps, so a host that left cannot keep badging rows. The badge gets a screen-reader label ("devbox, Disconnected") instead of reading the middle dot. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(runtime): let a paired desktop register its phones as delegated mobile devices A desktop paired to this runtime can now call pairing.delegatedMobileDevice.sync to make each of its phones an ordinary mobile-scope device it owns, so the phone mirrors that host through the desktop. Children are never pending, inherit the parent's reach, and die with the parent's grant. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(runtime): move the mobile device revoke primitive below dispatch Dispatch binds the caller-bound delegated-device context, so the revoke path it needs now lives in a layer under it as a pure move; this drops the abstract method seam. No behaviour change. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): keep a runtime grant until its delegated phones are revoked A failed child cleanup now leaves the parent in place so a retry can still find the children, and revokeRuntimeAccess lives beside the revoke it cascades to. Adds restart, rename and bounds coverage, shares credential minting, and drops the timing-dependent census. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): fail a delegated sync whose dropped phone cannot be revoked Matches the parent revoke: a child whose cleanup could not be saved is kept and the sync errors before minting anything, so the desktop retries instead of believing the server matches its list. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): relay a paired phone's server-workspace requests through the desktop A phone mirrors its desktop, including workspaces on the desktop's configured servers. The desktop now forwards a request that names `executionHost: runtime:<env>` to that server over one socket per (phone, server), signed in as the phone's delegated device; replies pass through untouched except stream ids. Hidden until the phone client ships: the relay capability is not advertised yet. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): type the single-host health fixtures inline so the test tsc passes Fixtures hoisted into consts widened their health maps to string, which the now-required hostHealthById rejects under the tests-typecheck ratchet; passing them inline keeps the union. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): state the desktop relay contract in its entry module main dropped docs/reference, so the relay's contract lives beside the code. Also pins that a server whose delegated sync fails (e.g. delegated_device_revoke_failed) is unavailable until a later request syncs it again. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): pin the relay's configured-server adapter The production hosts adapter was only typechecked; this pins that it resolves configured servers by exact id only (never a name or a removed server), fences by pairing revision and runtime identity, and stops reporting retirements once unsubscribed. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): re-record the repo-metadata goldens for SSH host health The settled metadata state now carries hostHealthById, and an SSH-only catalog now reads the target summaries so a lone unhealthy host can be badged. Scoped re-record via rpc-recording.mts. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): tighten the desktop relay after elegance review Reuses the shared E2EE socket opener, folds relay state into fewer fields, never sends a request whose link ended while opening, passes failure codes through, relays only this desktop's own phones, and drops the unused host-state reader until S2 needs one. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): bound relay frames and stop sticky relay verdicts Restores the 8 MiB frame cap on every remote runtime socket, re-checks an update-needed server on each new link so an in-place upgrade relays, answers a phone the server just declined without syncing again, and ends a link whose mirrored capability update cannot be queued. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): run targeted desktop-owned calls on the desktop The phone tags every call on a workspace screen with its execution host, so a paired-desktop method carrying `runtime:<env>` now runs locally instead of failing 'forbidden'. The Mac stays the only reader of the routing table. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): list the desktop's configured servers and their last workspace rows for the phone The phone needs the same host list the desktop shows, including servers that are offline or too old to relay to. mobileRelay.hosts.list/worktrees answer from the desktop's own status and its own worktree.ps fetches, cached per server and fenced by pairing, never from relayed replies. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): list server rows through one desktop fetch, with no side effect on listing The phone now always lists a server's rows through mobileRelay.hosts.worktrees, so the desktop's own fetch is both the live source and the cache, and stamping stays in main. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): fetch a server's full workspace list within a bound, and forget removed servers The desktop now asks for the phone's full-catalog limit, answers within 5s with the last rows when a server hangs, drops a server's rows when it is removed or re-paired, and checks the server's identity after the fetch so a reply from before a re-pair is never served. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): list managed servers as the desktop sidebar does, and hide rows mid-delete A server Orca manages on an SSH host now lists once under that host's name, or not at all while the host is reached over the SSH relay, using the sidebar's own folding. Rows the server is deleting are left out at reply time, as a phone asking the server itself would see. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): keep a disconnected server's last rows, and never join a re-paired server's fetch Retirement also fires on a manual disconnect or managed-server update, which wrongly emptied the phone's list; rows are now pruned only once a server is no longer configured. Fetches are shared per server identity, so a poll after a re-pair asks the new server right away. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(ci): run the remote-runtime WebSocket opener test on Node Under the bun vitest project, `ws` resolves to Bun's built-in client, which ignores maxPayload, so the frame-cap test hung. The opener only ships on Node/Electron, so run its test in the Node runtime project like its siblings. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile-relay): derive the phone allowlist from the routing table Adding a phone method now takes one edit (its route). A server-targeted call on a legacy non-E2EE socket is refused instead of running locally, and only execution-host methods enter the relay. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile-relay): drop unread freshness fields from server workspace lists The phone reads only the rows, so stale, fetchedAt, totalCount and truncated were dead wire weight. The desktop still serves a server's last rows while the server is unreachable. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): classify the mobileRelay RPC namespace as host-owned main now requires every runtime RPC namespace in the host-operation ownership table; the relay-hosts methods are served by the desktop the phone is paired to.
…roxy (#26942) Use the host HTTP client for usage, reset-credit reads and redemption, keep those account requests cookie-free, and pass configured proxy settings to the native Codex probe. WSL probes keep the guest's own proxy environment. Co-authored-by: petersindex <287557875+petersindex@users.noreply.github.com> Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
…ver sticks on pulling (#27409) * fix(ssh): remove the relay-only remote workspace sync so reconnect never sticks on pulling * refactor(ssh): drop snapshot apply tokens and terminal finalization only the sync used * test: drop leftover sync-status mock --------- Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(network): answer authenticated proxy challenges on cold desktop requests * fix(network): destroy a stalled proxy-fetch body on cancel --------- Co-authored-by: m4air <m4air@Mac.localdomain>
…rns (#26954) Co-authored-by: m4air <m4air@Mac.localdomain>
…l-list read (#27405) * fix(relay): keep seat polls off the cell-list read and report event-loop freezes The seat poller no longer waits on its database cell-list read before polling cells once it has a list, so a stalled read cannot starve the polls that feed a reserve cell's dead-man. Runtime metrics add eventLoopDelayMsMax: a single long freeze is one sample, so p99 read 68 ms across a 134 s staging freeze. Adds a pool test against a database that accepts and never answers, and a real-Postgres stop/start recovery test. * fix(relay): state the seat-poller gain precisely, justify the AddressInfo cast
…ng), adoptable deploy shape, event-loop stall alert (#27406) * fix(relay): director reaches Cloud SQL through a proxy sidecar (staging), gated event-loop stall alert On Cloud Run's built-in /cloudsql socket a connect() blocks the director's whole event loop until the connector's upstream dial ends, 10 s per attempt while the instance starts. Staging froze both directors for 134 s on 2026-10-11 and production four directors for 40-50 s on 2026-09-28. relay_director_cloud_sql_proxy_sidecar runs the Cloud SQL Auth Proxy beside the director on 127.0.0.1:5432, as the GCE cells already do, with its own TCP database URL secret. relay_event_loop_stall_alert adds the event_loop_ms_max metric and a 5 s stall alert. Both default off, so production plans unchanged; staging turns both on. * fix(relay): adopt a reviewed Terraform director shape on deploy; counter-based stall alert; cheaper probed sidecar - deploy-relay-blue-green: --shape-baseline-revision names the 0% revision a Terraform apply created; the deploy preserves that shape instead of the serving one. It must be the latest created, unserved revision on the serving image. Wired into the staging and production director workflows behind ADOPT_RELAY_DIRECTOR_TERRAFORM_SHAPE. - Event-loop stall alert is now a gated counter metric on eventLoopDelayMsMax>=5000. - Sidecar gets a /liveness probe and 0.25 vCPU (Cloud Run rejects only a total under 1 vCPU with concurrency above 1; checked with validateOnly against the API). - staging.tfvars pins the serving director's concurrency 1000 and timeout 3600 s, so the adopted apply shape does not fall to the 80 / 30 s defaults. * fix(relay): shape adoption refuses identity, topology, pruning and image changes; GOMAXPROCS=1 on the sidecar parseArguments now refuses --shape-baseline-revision with a runtime identity bootstrap, a director topology change or revision pruning, so every caller is covered, and the deploy refuses a shape baseline whose image differs from the serving one. Both director workflows also refuse the combinations up front. The proxy sidecar runs with GOMAXPROCS=1 to match its 0.25 vCPU quota. The long-hold alert comment is back above its resource.
…27412) * ci: drop the relay Windows process-tree job from desktop packaging * ci: refuse relay-era tags in a directly dispatched mac release build --------- Co-authored-by: m4air <m4air@Mac.localdomain>
…ge (#27416) Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: m4air <m4air@Mac.localdomain>
#27396 deleted the write-only registry that consumed it; nothing sets the option now, and its doc comment implied a cancellation path that no longer exists.
…27419) Co-authored-by: m4air <m4air@Mac.localdomain>
* Add manual worktree storage cleanup command * Validate cleanup exclusions and preserve failure diagnostics
Fold the empty owner-routing baseline into the focus-read ratchet. The three deleted focus-routing helpers stay banned as a forbidden-name list. Co-authored-by: m4air <m4air@Mac.localdomain>
…7423) A layout stream's cleanup is now registered under a key that includes the subscriber's connection, and layout.unsubscribe builds that key from the caller's own connection, matching session.tabs.unsubscribe. Another connection can no longer end a stream by passing its id.
…away (#23459) * fix(linux): stop the AppImage crashpad handler before the mount goes away Crashpad's handler runs from the AppImage FUSE mount and outlives the main process, so it SIGBUSes once the runtime unmounts it. On a validated AppImage running from its own mount, SIGKILL this launch's handler from the main process's exit hook. Fixes #22542 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: clarify AppImage Crashpad exit hook --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The relay client is gone, so the three relay-ladder cells and the @orcad-cli-relay-kept matrix case could not run. Delete them, their last helper (ssh-test-target-connection), a stale path filter and the unused build:relay step; rename the runner to invoke-windows-host-cells.ps1. Co-authored-by: m4air <m4air@Mac.localdomain>
…27424) * feat(daemon): observe-only fork gate for automatic terminal restores * fix(daemon): keep a canceled fork charged until reaped; never strand a user resume --------- Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: m4air <m4air@Mac.localdomain>
…#27435) Co-authored-by: Sergey Tiron <1ivi41337@gmail.com>
Removes seven modules that only their own tests import, dead orcad exports and protocol constants, spells orcad's configuration exit code once, reuses the shared errorMessage helper, and drops links to the deleted docs/reference folder. Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Nothing mints an SSH-bridge credential since the relay client was removed (#27214), so the 'ssh-bridge' caller kind, its host binders, result filter, the CLI bridge-mode policy and the ORCA_SSH_BRIDGE_CREDENTIAL env read could never run. The allowRemoteCliControl target field stays for older peers. Co-authored-by: m4air <m4air@Mac.localdomain>
* feat(daemon): pause automatic restores after a same-boot crash * fix(daemon): per-version crash marker, owner-only writes, orderly login retirement, no Windows boot-time guess --------- Co-authored-by: m4air <m4air@Mac.localdomain>
…pp (#28007) * feat(serve): run the --supervise supervisor from a copy outside the app * fix(serve): record supervisor copy users beside the copies so a cleanup never races a fresh copy * fix(serve): remove a partial supervisor copy when copying fails * test(serve): skip the unreadable-copy case where mode 000 does not block reads --------- Co-authored-by: m4air <m4air@Mac.localdomain>
…progress (#28015) Direct AppImage serve now unpacks its stable copy once, under one overall deadline that also bounds the registration lock wait and both extraction attempts, prints progress to stderr, and refuses fast when the private cache is not writable, not executable, or too full. Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude <noreply@anthropic.com>
…es are gone (#28011) * fix(daemon): stop giving new terminals to a daemon whose AppImage files are gone On Linux AppImage, a terminal daemon can outlive the mount it runs from and keep answering health checks until it SIGBUSes. Orca now reads the daemon's /proc exe link and mount table at adoption: an idle severed daemon is replaced; a busy one is kept for its sessions but quarantined, so fresh terminals use the local provider and a healthy RPC never re-admits them. Status and a verified restart are available over RPC and as `orca terminal-daemon status|restart`. Co-Authored-By: Claude <noreply@anthropic.com> * fix(daemon): lift the severed quarantine once a new daemon owns the endpoint Co-Authored-By: Claude <noreply@anthropic.com> * fix(daemon): a severed-daemon restart counts fallback terminals and never stops a replacement The restart re-checks the authenticated endpoint owner before any teardown and again at shutdown, and never falls back to a pid kill when targeted. Status counts the local fallback terminals the restart also ends. Co-Authored-By: Claude <noreply@anthropic.com> * chore(daemon): merge duplicate import Co-Authored-By: Claude <noreply@anthropic.com> * fix(runtime): classify the terminalDaemon RPC namespace as host-owned Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude <noreply@anthropic.com>
…files are gone (#28021) Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude <noreply@anthropic.com>
) CodeBuddy spawns hooks registered with shell: 'powershell' as a bare `pwsh`, so on Windows without PowerShell 7 every hook failed with spawn pwsh ENOENT and the failed UserPromptSubmit hook blocked prompts. Register CodeBuddy's hooks the way Claude's are: the managed .cmd path on cmd-safe profiles (runs under CodeBuddy's Git Bash or Windows PowerShell default shell) and the absolute powershell.exe encoded launcher otherwise. The next install rewrites existing registrations. Fixes #27182 Co-authored-by: Orca Worker <orca-worker@localhost>
…re (#27755) * fix(browser): end a page's mount admission whenever it leaves the store A background-opened page's start admission was released only by the two close actions, so a bulk worktree purge (listing refresh, repo or folder removal) dropped the page while its admission stayed in the set. Release it in the one cleanup every page removal already runs through (destroyRemovedBrowserWebview), and drop the per-path copies. STA-7436 * refactor(browser): one removed-page sweep shared by the app and its test Moves the store sweep that destroys removed pages into destroyWebviewsRemovedFromStore so the admission regression test runs the same code the app runs, and adds an eviction case. * test(browser): assert pages were admitted before the eviction case
…ca (#27731) * perf(notifications): save mobile dismissal history off the main thread Every agent notification recorded its id in mobile-notification-dismissals.json through the secure-file writer. On Windows that writer runs about five synchronous icacls launches per write, so each notification froze every Orca window (STA-9692). The file holds notification ids, sequence numbers and session ids, not credentials, so it now goes through the existing AtomicSnapshotWriter: async temp-plus-rename, mode 0600, bursts coalesced into one write of the latest entries. Memory stays authoritative, so delivery and retirement are unchanged. The pending write is flushed in the quit barrier after chat and RPC teardown, and in the pre-relaunch hook, since renderer relaunch exits without the quit pipeline. * perf(notifications): flush dismissal history when a headless host shuts down orcad never runs the desktop quit barrier, so its cleanup list awaits the pending dismissal write after RPC stops. * fix(persistence): drain a snapshot write requested just after a drain ends AtomicSnapshotWriter cleared its pending-write promise one microtask after the drain loop saw no more requests. A write() in that gap set writeRequested, got the settling promise back, and nothing drained it, so the newest bytes stayed unwritten and waitForPendingWrite() (the dismissal store's flush) resolved without them. The drain now clears the pending promise in the same tick its loop exits, on success and failure, so a later write() always starts a new drain. * test(notifications): flush dismissal writes before deleting temp state dirs Dismissal history now saves asynchronously, and the writer recreates its directory before each write. Tests that deleted their temp dir with a write still pending left recreated dirs behind in the system temp folder and could race the delete on Windows. They now await the controller's flush first. * perf(notifications): close dismissal history at committed exits The shutdown flush only waited for writes already queued. A notification recorded after it (orcad cleanups still running, or the 150 ms before a renderer relaunch calls app.exit) started an async write the exit dropped, so the phone could keep an alert the desktop could no longer withdraw. The store's flush() becomes close(): it waits for the pending write, and every later record saves synchronously through the snapshot writer's existing writeSync (plain fs, no icacls; a failure only warns). It is called only where the exit can no longer be vetoed: the will-quit teardown, a new exit-only hook that app:relaunch runs before app.exit(0) (restart and profile switches share onBeforeRelaunch but quit through will-quit), and orcad's cleanup after RPC stops. The quit test now holds chat teardown and RPC stop and proves the close starts only after both settle, in either order. The orcad push-startup runtime mock gains the method the entry now calls. * fix(notifications): bound the dismissal close before a renderer relaunch app:relaunch waited on the dismissal store's close with no limit, so a stalled userData disk left Restart stuck on "Restarting…" with a half-quit app saving synchronously. The close now races a 2 s cap through the existing settleWithinMs helper, the same cap the restart path's auth preservation uses, and logs when it gives up. * test(notifications): keep async dismissal writes under test between reads Mid-test disk reads used closeDismissals() as a barrier, which switched every later record to the exit-only synchronous path. The store gains a non-closing flush() (close() = mark closed + flush), exposed on the controller as flushDismissals for tests; mid-test barriers use it, and close stays only where a test simulates an exit or teardown. Also notes why the writer's pendingWrite assignment is safe, renames a store test to what it covers, and groups the orcad cleanup-order test under a describe.
…unches (#27756) * ci: ratchet synchronous child-process launches in main and shared code The Electron main process freezes every window while a synchronous child process runs, and on Windows each launch is slow because antivirus scans it. Nothing stopped new synchronous launches from landing in main-process code, and the spawn observer behind ORCA_MAIN_THREAD_DIAGNOSTICS only saw async spawns, so the worst blocks were invisible to it. Ratchet: check:main-sync-spawn-ratchet parses production src/main and src/shared files (test-only paths excluded) and fails when a file not in config/main-sync-spawn-baseline.txt reaches execFileSync, spawnSync or execSync from child_process, runProcessSync from @orca/process-host, or one of the synchronous secure-file writers (they run icacls on Windows). Named, aliased, namespace, default, require() and import() forms count. A baseline entry whose file no longer launches synchronously fails as stale, so the list can only shrink. It runs in lint and in the PR static-analysis job next to the process-host import check. Observer: runProcessSync now reports to the spawn observer, timing the whole call (that is how long the thread was stuck) and reporting from a finally so failing, throwing and timed-out children are counted too. Runtime behavior is unchanged except that the diagnostics observer now sees synchronous launches; with no observer registered the sync path pays one extra branch. * ci: count sync launches per file and gate the repo's sync wrappers The sync-spawn ratchet only tracked which files launched synchronously, so a file already on the list (wsl.ts, win32-utils.ts, git-exec-file.ts) could add more launches unnoticed, and new callers of the repo's own synchronous wrappers passed CI. The baseline now stores a per-file count of uses, in the same format as the focus-read ratchet: a rise or a new file fails, a lower or zero count fails until `pnpm check:main-sync-spawn-ratchet --prune` lowers it, and --prune never raises an entry. Every value reference to a gated function counts (call, hand-off, alias and its uses, re-export); imports alone count nothing. Gated wrappers: gitExecFileSync (and the git/runner re-export), restrictWindowsPathSync, hardenSecurePath when called with sync: true, grantDirAcl, getWslHome, listWslDistros, wslUncDirectoryExists, isWslAvailable (and the wsl.ts re-export), isPwshAvailable. Their 18 current caller files join the baseline. Failure messages now give advice per family: the async process-host API for child-process launches, the async twin for wrappers, and for the secure writers a note that no async secure writer exists yet, so the sync one stays and must never become a plain write (that drops the Windows ACL hardening). Also refresh two stale blockMs descriptions and drop a no-op line from the spawn-observer "stays silent" test. * ci: count hardenSecurePath unless visibly async, and tighten ratchet tests hardenSecurePath was counted only when `{ sync: true }` was written inline, so moving the options into a variable or a spread dropped the count and the check then asked for a prune. It now counts every use except a call with no options or a spread-free object literal whose `sync` is absent or false. Also: resolve backslash paths in the importing file as well as the specifier; still visit heritage and instantiation expressions (`extends mixin(fn)`, `fn<T>`); state the reference-only counting limit; and point the secure-writer advice at raising this file's baseline count. Tests cover the hardenSecurePath shapes, the alias chains that need the fixpoint loop, and a prune that must not raise a grown row. * test(ci): pin the main sync-spawn ratchet background step * fix: count secure hardening calls with spread arguments
… load (#27754) * fix(browser): end a background page's mount admission after its first load A page opened in the background is admitted so its guest starts, but the admission was only released on close. Its overlay stayed laid out (invisible) in the visible workspace and its browser chrome never unmounted in hidden workspaces. Release the admission when the guest's first load settles, so the normal active-page and paint-retention rules own the page afterwards. STA-7435 * test(browser): pin how a released mount admission parks a background page Covers the overlay contract the release relies on: in a hidden worktree the page's browser chrome unmounts once the admission ends; in the visible worktree it stays mounted and only its slot stops being laid out. Also corrects the page-record comment that still said background tabs sit idle until shown.
…ete (#27625) * fix(browser): settle WSL tunnel reads so responses past 256 KiB complete The WSL execution route hands the deferred socket a credit-accounted tunnel duplex, but the deferred socket forwarded its data without settling it, so the relay's 256 KiB receive window was never replenished and larger responses stalled. Settle each chunk once tunnel-socket listeners have taken it; their pause() still holds the source, so unsettled bytes stay bounded by the window. Fixes #27356 * test(browser): bound the WSL backpressure wait so a regression fails instead of hanging * test(browser): keep unchanged WSL route test lines in their original form --------- Co-authored-by: Orca Worker <orca-worker@localhost>
…i, dsh, Kiro, Droid and Command Code (#27717) * perf(hooks): register direct .cmd hooks for Gemini, DSH, Kiro, Droid and Command Code on Windows On a cmd-safe profile path these agents now register the managed .cmd path itself instead of a `powershell.exe -EncodedCommand` launcher, so each hook event no longer pays a second PowerShell start-up on top of the runner's own shell. Profiles the bare path cannot carry (spaces, non-ASCII, UNC) keep the encoded launcher. Same pattern as Claude (#18875) and Muse (#26755). Refs #20913 * refactor(hooks): one Windows batch-hook helper and one payload-delivery table - Add wrapWindowsBatchHookCommand (bare .cmd path, encoded launcher otherwise) and use it for Muse, Gemini, DSH, Kiro, Droid and Command Code. - Fold the five agents into windows-hook-payload-delivery.test.ts as rows run under each vendor's shell, from a plain and a spaced profile, with a stale encoded registration seeded per row; drop the separate runner suite and its CI list entries. - Replace the Windows-only per-agent registration tests with ones that stub process.platform, so they run on every CI OS. - Fix stale launcher, helper and DSH comments. Refs #20913 --------- Co-authored-by: Orca Worker <orca-worker@localhost>
* Show recorded turn diffs inline in native chat * Resolve only selected turn diff tool pairs
…view (#27184) * feat(terminal-pane): header button resumes the CLI session in a new native chat The pane header's "Show chat view" toggle is replaced by the tab menu's "Resume in New Native Chat" move. The Session History lookup runs on click, so an always-visible header never scans in the background. A pane already in chat view keeps its "Show terminal" way back. * fix(terminal-pane): fold the resume gate into the pane's existing store subscription Keeps a mounted terminal pane inside its pinned store-listener budget. * fix(terminal-pane): resume the pane's own conversation, open the chat that already holds it The header "Resume in New Native Chat" button gated on chat-view eligibility but looked up the tab's best conversation, so a Grok pane beside a Claude pane resumed the Claude conversation, and Grok/OpenCode panes showed a button that always failed with a "try again" toast. - Map one pane to its conversation with the tab-title mapping narrowed to that pane (same sources, same Claude/Codex rule), for both the button's visibility and its click; this also keeps the button after the agent exits, as the tab menu does. - A conversation a native chat already holds now matches its history row; the move's gate still refuses it (tab menu unchanged, pinned in the parity test), and the button opens that chat. - Distinct messages: nothing saved yet, cannot be resumed in a native chat, or the existing generic failure. The click forces a fresh scan like the panel's Refresh does. - Call the plain resume launch directly; drop the shared actions module and restore the tab menu. - A tab closed during the lookup no longer opens a chat. * fix(terminal-pane): hide Resume in New Native Chat where the chat route is refused On a local workspace whose project runs in WSL (or needs runtime repair), the header button showed on Claude/Codex panes, but native chat cannot launch there, so every click said the conversation can't be resumed while the tab menu offered nothing. The button's visibility now also asks the same launch-route check the Session History row gate uses (structuredAgentSessionLaunchFeasible). * fix(terminal-pane): offer the header resume only while the pane's agent is running The tab menu now offers Resume in New Native Chat only while the tab's agent runs (#27520). The header button still found its pane's conversation from the retained or sleeping record an exited agent leaves behind, so it stayed after exit. The pane lookup now honours the same runningAgentsOnly option, and both the button's visibility and its click pass it. * refactor(terminal-pane): the header's pane lookup reads only the running agent Every caller of resolvePaneAiVaultTitleRequest asked for running agents only, so its retained and sleeping sources were never read. The pane lookup now maps just the pane's live agent, and collectAiVaultTitleRequests returns to main's shape, sharing only the live-agent mapping with it.
…was working (#27821) * fix(native-chat): light the tick of a message sent mid-turn on the message rail A message sent while the agent is working joins the running turn's key, and the rail lit ticks by turn key, so that message's tick never lit: not at the bottom, not after clicking it. Light the newest tick at or above the read row within its turn instead. * fix(native-chat): light the nearest tick above the read row, and keep a clicked tick lit Review round 1. The within-turn walk stopped at a row of no turn landing in the running turn, and could return a tick from the turn above. The lit tick is now the newest loaded rail item at or above the row at the fold, falling back to its turn key above the first loaded one (that prompt is in the outline). A jump that cannot bring its row to the top lands clamped at the bottom, and the settled position report then lit the last tick instead of the clicked one. A clicked tick now stays lit until the reader scrolls or starts another navigation (the existing reader-input path), or the set of prompts changes. Rail picks and the start of every navigation move into their own hook, as the list is at its line limit. * fix(native-chat): hold a clicked rail tick by position, not by reader input Review round 2. Releasing the clicked tick on reader input needed every scroll source to report itself, and several do not (scroll-to-top button, inline diff reveal, prompt refold, selection-drag autoscroll); releasing it whenever the prompts changed dropped it on every head trim of a long live session. The hold now records where the jump settled, at the first idle read after the click, and keeps the clicked tick lit while the view rests there. Any movement, whatever caused it, releases it, as does the clicked message leaving the rail. Drops the navigation wiring and the hook it was moved into. * fix(native-chat): anchor a clicked rail tick's hold to its row on screen Review round 3. Holding while scrollTop stayed put recorded the reader's spot when they kept scrolling within the idle period of the jump, kept an off-screen row lit after the viewport shrank, and let go when a row above re-measured late and shifted scrollTop with the content. The hold now records how far below the viewport top the clicked row settled, and keeps the tick lit while the row is still on screen at that offset.
* [CI] Remove wall-clock waits and stabilize GC fairness check * [CI] Include the final GC timer gap in the fairness check * [CI] Advance test clocks and preserve cleanup checks
…#28045) * fix(chat): name orchestration worker chats after their task title Worker chats were skipped by chat naming and stayed "Claude Chat". Name them from the task they were started for instead, with no model call. * Fix legacy worker chat titles without prompt generation * Remove unrelated lockfile dependency
…result (#27580) * fix(native-chat): keep a 'chats resumed' bottom-bar entry until the result is seen After every chat resumed, the bottom-bar entry vanished the moment the run finished, leaving a fading toast as the only way back to the list. Show 'N chats resumed' while a finished run's summary has not been shown yet, reusing the run's existing finishedViewShown flag so the entry ends exactly when the summary is viewed and closed. * Keep resume results until their rows are seen * Retire viewed empty resume runs * Show finished resume rows after in-flight view settles
…ary (#28054) #27756's script imported baseline helpers from check-focus-read-ratchet.mjs after #27788 had moved them into ratchet-baseline.mjs, so it crashed on start and failed every PR that merges main. Use the library's helpers, and record the two daemon-process launches from #27769 that the broken check never saw. Co-authored-by: Orca Worker <orca-worker@localhost>
* feat(native-chat): wrap long lines in code blocks Code blocks in native chat now have a wrap-lines toggle next to Copy. It is off by default, per block, and not persisted; when on, the block wraps long lines instead of scrolling sideways. * fix(native-chat): give each document code block its own component The document pre renderer called the code-block renderer as a function after an early return, so a renderer with hooks ran them as the pre's own hooks and React saw the hook order change when a fence switched between diagram and code. * fix(native-chat): keep a block's line wrap when its row scrolls away Wrap lived in the code block's own state, so a row that windowing unmounted, or a growing reply re-cutting its top-level blocks, came back unwrapped and shorter than its measured height. The flag now lives in the transcript's disclosure store, keyed by the row and the fence's offset in the whole message, which appended text never moves. Outside a transcript the block keeps local state as before. * fix(native-chat): key a block's line wrap by its line, not its character offset While a reply streams, its last block is parsed after the streaming fixes escape characters such as x~y mid-line, so a later fence's character offset moved by one when the block was next parsed as written (the reply finishing, another block following, or the reply merging into one block), and the wrap reset. Those fixes never add or remove a line break before a fence, so the block splitter now records each block's starting line and the wrap key uses the fence's line in the whole message. * fix(native-chat): keep line wrap in plan, reasoning and approval cards too Plan cards and reasoning bodies rendered their markdown without a row key, so their code blocks fell back to local wrap state and lost it when the row scrolled away. Reasoning text now uses its existing disclosure key, shared by the live line and the finished row, and a plan card uses plan:<message id>, so neither can share a key with the reply of the same message. The approval card sits in the composer slot, outside the transcript and its store, and is never windowed, so it keeps local state.
…27714) * perf(hooks): register direct .cmd hooks for Gemini, DSH, Kiro, Droid and Command Code on Windows On a cmd-safe profile path these agents now register the managed .cmd path itself instead of a `powershell.exe -EncodedCommand` launcher, so each hook event no longer pays a second PowerShell start-up on top of the runner's own shell. Profiles the bare path cannot carry (spaces, non-ASCII, UNC) keep the encoded launcher. Same pattern as Claude (#18875) and Muse (#26755). Refs #20913 * fix(kimi): register a cmd.exe-runnable hook on Windows Kimi runs hooks with asyncio.create_subprocess_shell, which is cmd.exe on Windows. Orca registered a POSIX `if [ -f ... ]` command there, so cmd failed with "-f was unexpected at this time" before reaching the script and, since Kimi fails open, no status event was ever sent. On win32 write a cmd+curl kimi-hook.cmd and register it the way the other direct .cmd agents do: the bare forward-slash path on a cmd-safe profile, the encoded launcher otherwise. The ownership matcher already covers every extension of the stem, so the old kimi-hook.sh entry is replaced on the next install. SSH remotes keep the POSIX script. Kimi joins the direct .cmd runner suite as a row run the way CPython's shell=True does, and every row now also runs from a profile with a space (the encoded launcher) and outside an Orca pane. * refactor(hooks): one Windows batch-hook helper and one payload-delivery table - Add wrapWindowsBatchHookCommand (bare .cmd path, encoded launcher otherwise) and use it for Muse, Gemini, DSH, Kiro, Droid and Command Code. - Fold the five agents into windows-hook-payload-delivery.test.ts as rows run under each vendor's shell, from a plain and a spaced profile, with a stale encoded registration seeded per row; drop the separate runner suite and its CI list entries. - Replace the Windows-only per-agent registration tests with ones that stub process.platform, so they run on every CI OS. - Fix stale launcher, helper and DSH comments. Refs #20913 --------- Co-authored-by: Orca Worker <orca-worker@localhost>
…nd UTF-8 safe (#27718) * perf(hooks): register direct .cmd hooks for Gemini, DSH, Kiro, Droid and Command Code on Windows On a cmd-safe profile path these agents now register the managed .cmd path itself instead of a `powershell.exe -EncodedCommand` launcher, so each hook event no longer pays a second PowerShell start-up on top of the runner's own shell. Profiles the bare path cannot carry (spaces, non-ASCII, UNC) keep the encoded launcher. Same pattern as Claude (#18875) and Muse (#26755). Refs #20913 * fix(kimi): register a cmd.exe-runnable hook on Windows Kimi runs hooks with asyncio.create_subprocess_shell, which is cmd.exe on Windows. Orca registered a POSIX `if [ -f ... ]` command there, so cmd failed with "-f was unexpected at this time" before reaching the script and, since Kimi fails open, no status event was ever sent. On win32 write a cmd+curl kimi-hook.cmd and register it the way the other direct .cmd agents do: the bare forward-slash path on a cmd-safe profile, the encoded launcher otherwise. The ownership matcher already covers every extension of the stem, so the old kimi-hook.sh entry is replaced on the next install. SSH remotes keep the POSIX script. Kimi joins the direct .cmd runner suite as a row run the way CPython's shell=True does, and every row now also runs from a profile with a space (the encoded launcher) and outside an Orca pane. * fix(copilot): run the Windows status hook as a direct curl .cmd Copilot runs a Windows hook as `pwsh|powershell -nop -nol -c <command>`. Orca registered an encoded `powershell.exe -EncodedCommand` launcher there that started a second PowerShell to run copilot-hook.ps1, which posted with Invoke-WebRequest: two PowerShell starts per event (~850-900 ms median), and the .ps1 read stdin through the console code page, so non-ASCII prompt text arrived as "caf?? ...". On win32 write copilot-hook.cmd (answer `{}`, refresh the endpoint, env guard before stdin, curl form POST with hookEventName) and register its bare path with the event as its argument; non-cmd-safe profile paths keep the encoded launcher, which now sets ORCA_COPILOT_HOOK_EVENT for the .cmd. The managed matcher covers every extension of the stem, so old .ps1 entries are replaced on the next install while user hooks stay. SSH remotes keep the POSIX script. Copilot joins the direct .cmd runner suite as a row run under both PowerShells, checking the byte-exact payload and the posted hookEventName. Refs #20913 * refactor(hooks): one Windows batch-hook helper and one payload-delivery table - Add wrapWindowsBatchHookCommand (bare .cmd path, encoded launcher otherwise) and use it for Muse, Gemini, DSH, Kiro, Droid and Command Code. - Fold the five agents into windows-hook-payload-delivery.test.ts as rows run under each vendor's shell, from a plain and a spaced profile, with a stale encoded registration seeded per row; drop the separate runner suite and its CI list entries. - Replace the Windows-only per-agent registration tests with ones that stub process.platform, so they run on every CI OS. - Fix stale launcher, helper and DSH comments. Refs #20913 --------- Co-authored-by: Orca Worker <orca-worker@localhost>
An exited node.exe copy can stay locked for a moment on Windows, so the afterAll rm failed the hourly build's windows-11-arm persistence job.
…focus doesn't freeze the UI (#27707) * fix(rate-limits): spawn the Codex usage probe off the main thread On Windows, creating a cold standalone codex.exe can spend seconds in code integrity checks on the calling thread. The usage probe runs on window focus, so that froze the UI. The probe's child is now created and piped on a worker thread and proxied back to the main thread. Fixes #27362 * refactor(rate-limits): type the worker's children through @orca/process-host * fix(rate-limits): kill an orphaned Codex probe by pid when its worker dies Once the worker thread that owns the probe's child is gone, kill() can no longer reach the child through it. The proxy now falls back to a pid-addressed kill; the Codex probe uses the app-server tree kill so a node launcher's codex.exe child goes too, while SIGTERM stays a graceful root signal. * refactor(build): move the main build's entry map out of electron.vite.config.ts The new worker entry pushed electron.vite.config.ts past the 300-line limit. The Rollup input map for the main build now lives in its own module. * refactor(rate-limits): drop the orphan pid kill and trim the worker child-process stand-in A worker thread's death closes its children's pipes, so an abandoned Codex probe already gets stdin EOF, its graceful stop. The pid-addressed orphan kill was redundant with that and with terminateCodexProbeChild's own gated Windows tree kill. Its hard kill also bypassed the tree-kill gate's recycled-pid refusal by turning the handle-addressed fallback into a pid kill. - Remove orphan-pid-kill, killOrphanedCodexProbe, the injected killOrphan, and the orphaned flag. kill() now reports false once a child is no longer tracked. - Process-creation failure is one 'spawn-error' event that the stand-in expands into error/exit/close, replacing the duplicated triple and the exported exit code. Stdin errors arrive as child errors, as the probe already treated them. - Fold both suites into one over a real MessageChannel and real children. Add a real-worker test showing that terminating or crashing the worker lets the child exit on stdin EOF. * test(rate-limits): run the worker child-process suite under Node The suite asserts Node behavior that Electron main relies on: a failed spawn closes with a null signal, and a dead worker thread closes its children's pipes. Bun reports an undefined signal and, on Linux, leaves the pipes open, so the Bun lane failed on runtime differences, not on the product code. * test(rate-limits): count a reaped-by-nobody zombie as an exited child On Linux and macOS a child whose worker thread died gets stdin EOF and exits, but libuv reaps a child only through the handle on the loop that spawned it. Once that loop is gone, nothing waits on the pid, so it stays a zombie and kill(pid, 0) still succeeds. The real-worker tests now treat a zombie as exited, and still require the child's EOF marker. --------- Co-authored-by: Orca Worker <orca-worker@localhost>
* feat(chat): read one saved row per subagent Adds the singular `subagent` journal block (one row per child, revised in place, with a plain-text twin for older clients) and teaches every reader to draw it: desktop and phone fold a turn's sibling rows into the existing "Ran N subagents" row, the client and off-page rosters name children from their own rows, the reopen pass settles a row a dead host left working, and the phone and worker-transcript wires bound it. No producer writes the block yet; that switch lands in the follow-up. * fix(chat): open subagent background flag, separate nested group ids, page window skips subagent rows - background is an open boolean so a later build's value never fails a chat load - a helper's own helpers fold under a groupId of their own, so the phone opens them apart - each subagent's own row rides along with the page window instead of counting as conversation * refactor(chat): a subagent's group entry as one object
Preserve all 52 Markdown cases while consolidating one file and importing existing direct owners. Advance editor debounce windows through public selective timers, exclude unrelated lint tests from Performance PR triggers, and remove a pane assertion case entailed by retained coverage.
* refactor: move provider-retry pieces into a sibling words file
Pure move of the six provider-retry translation pieces out of
agent-session-failure-words-text.ts, which sits at the 300-line limit.
No key or behaviour change.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(chat): name the agent when it refuses a message
"The provider did not accept this message" becomes "{{agent}} didn't
accept this message" ("The agent" when the name is unknown), in English
and every locale. The providerRejected failure kind is unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Require Codex 0.136.0 for native chats
* Add host-owned Codex install and update actions
* Bind Codex checks and maintenance to the selected installation
* Resolve Windows updater cleanup through system helpers
* Use one child environment for Codex checks and launch
* Expire Codex readiness and reconcile current host jobs
* Resolve Codex checks against the child command search path
* Restrict global npm repair to the selected Codex install layout
* Pin the host updater directory through Windows supervision
* Synchronize Codex maintenance fixtures on readiness
* Unify Codex notices and recover version-refused chats
* Route Codex recovery to the operation that remains
* Preserve Codex setup execution errors in the job log
* Preserve UTF-8 in Codex setup logs
* Remove unused Codex host installation detector
* Require host administration for Codex maintenance
* Show a missing Codex explanation once
* test(native-chat): give scripted Codex runtimes their installation fact; keep the registry out of the relay
Runtime tests that drive a scripted Codex app-server still ran the new
version check against whatever codex was on the machine's PATH: refused
on CI (no codex), and on a dev machine it ran the real codex --version.
A scripted transport now must state the Codex it stands for
(readCodexInstallation), asserted at install, and the tests get it from
one scriptedCodexTransport fixture.
The relay's Codex maintenance path reached @orca/windows-registry through
resolvePathEnvKey's re-export in windows-environment-path; import it from
its registry-free module instead.
Also update the catalog-probe test for the shared child environment.
* refactor(native-chat): drop the Update Codex action; keep Install for a missing Codex
A too-old Codex now gets only the plain message ("Codex X is too old for
chats. Update to 0.136.0 or newer.") with no button and no job. The
existing rechecks (window focus, the <=30 s expiry of a saved result)
retry the refused chat once the user has updated Codex themselves.
A missing Codex keeps the Install Codex button: `npm install -g
@openai/codex` with the npm on the chat's PATH, as a host-run job with
its log, then a recheck and recovery. A configured Command that is
missing gets the message only, since a global npm install would not
land at that path.
Removed: the `codex update` command and its 0.126 floor, manual
instructions, npm layout and launcher identity detection, the action
descriptor (install|update) and its titles, the legacy capability, and
the SSH relay maintenance path (structured chat never runs over SSH or
WSL), so the relay preflight handler is back to main's.
* refactor(native-chat): keep Install Codex in Settings only
When Codex isn't installed, main already hides Codex from the New tab menu,
so the in-chat Install button was almost never reachable. Settings → Agents
already shows "Codex isn't installed." with Install Codex.
- Chats and the new-workspace composer now only state "Codex isn't
installed." (no button, no install job from the chat).
- The Install action, its busy spinner and the host-busy subscription move
into the Settings Codex row, the only place that starts the job.
- The store no longer re-checks every other workspace context when a job
finishes. A refused chat recovers through the same re-check a too-old
Codex uses (window focus, or the <=30 s expiry of a saved result).
- The chat recovery key no longer includes the install job id.
- Tests: Settings drives the install; a new test proves a chat refused for
a missing Codex recovers through focus after a Settings install, not
through the job; the expiry recovery test covers missing and too old.
* refactor(codex): check chat compatibility when acquiring the child
* Restore workspace submit guard and simplify Codex maintenance
* Keep only the Codex chat minimum version
… Devin, ZCode and Antigravity (#27713) * fix(agent-hooks): register forward-slash Windows hook paths for Grok, Devin, ZCode and Antigravity These four agents registered their native-Windows status hook as a bare backslash path. A Git Bash hook host strips the backslashes, the command exits 127, and no status event reaches Orca. Use the same direct forward-slash form Claude, Codex and Muse already register, keeping the cmd-safe gate and the encoded PowerShell fallback. Existing backslash entries still match the managed-command matcher, so the next install replaces them and keeps user hooks. * perf(hooks): register direct .cmd hooks for Gemini, DSH, Kiro, Droid and Command Code on Windows On a cmd-safe profile path these agents now register the managed .cmd path itself instead of a `powershell.exe -EncodedCommand` launcher, so each hook event no longer pays a second PowerShell start-up on top of the runner's own shell. Profiles the bare path cannot carry (spaces, non-ASCII, UNC) keep the encoded launcher. Same pattern as Claude (#18875) and Muse (#26755). Refs #20913 * test(agent-hooks): expect forward-slash Antigravity hooks after the Unicode upgrade ASCII profiles now register the direct forward-slash wrapper path, so the upgrade-delivery test expects that form and also runs it through Git Bash. Unicode profiles keep the encoded launcher. * test(agent-hooks): assert the registered hook lives under the mocked home Pins that the os mock reaches the agents' node:os imports, so the host test never installs into the real profile. * refactor(hooks): one Windows batch-hook helper and one payload-delivery table - Add wrapWindowsBatchHookCommand (bare .cmd path, encoded launcher otherwise) and use it for Muse, Gemini, DSH, Kiro, Droid and Command Code. - Fold the five agents into windows-hook-payload-delivery.test.ts as rows run under each vendor's shell, from a plain and a spaced profile, with a stale encoded registration seeded per row; drop the separate runner suite and its CI list entries. - Replace the Windows-only per-agent registration tests with ones that stub process.platform, so they run on every CI OS. - Fix stale launcher, helper and DSH comments. Refs #20913 * refactor(agent-hooks): register Grok, Devin, ZCode and Antigravity through wrapWindowsBatchHookCommand Use the shared batch-hook helper from #27717 instead of an inline direct-or-cmd fallback. A UNC home is cmd-safe but has no drive letter; the old fallback registered it as a bare backslash path that Git Bash cannot start, while the helper sends it to the encoded launcher. wrapWindowsCmdHookCommand has no production callers left, so it is removed and its tests point at the helper. Grok, Devin and ZCode join the Windows payload delivery table with a legacy backslash entry to sweep, replacing the separate host test, and the Antigravity migration test now runs on every platform. --------- Co-authored-by: Orca Worker <orca-worker@localhost>
…ile downloading (#28056) The OTA download screen now shows a progress bar over bytes received, labelled "Downloading the latest app" with a "from <host> · X.X of Y.Y MB" caption, instead of a spinner with raw file and byte counts. Related offline and failure copy says "the latest app" instead of "the workspace". The bar barely moved before because React never committed progress during the download. SHA-256 of each asset ran in JS (3.8 s of a 5.7 s download), and React Native's scheduler runs queued native callbacks ahead of normal-priority renders, forcing a render only after 5 s. Assets are now hashed with expo-crypto's native digest (~5 ms total), so progress renders every 100–250 ms at 1 MB/s. Verified on an Android emulator and the iOS simulator. The bar glides between bursts of progress (moving average of burst gaps, clamped 200–2000 ms) and is ShellWaitingFrame's indicator, so the label keeps the same position as on "Opening workspace". On an uncapped local link, decryption still keeps the JS thread busy and the bar can jump; that is left for a follow-up.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )