Commit Graph
18 Commits
Author SHA1 Message Date
Neil b5814d058e Avoid rescanning shared chunks in the plain Node build guard (#24717)
Reuse a successful unchanged chunk-code verdict within one synchronous writeBundle invocation, while preserving traversal and current-code reads.
2026-10-03 15:31:29 -07:00
+33 5e6587197a fix(opencode): keep shared-session TUI activity with its owning pane (#24962)
* fix(opencode): bind legacy attach status to its TUI pane

Adapt the official v1 TUI API to the existing pane reporter and learn structural session identity only after execution-host admission. Preserve known creators, gate new reports to capable hosts, and register the legacy TUI entry without changing user settings or overlay targets.

Credit werlang for the same-directory investigation in #22838 and #22847; no source from that PR is copied.

(cherry picked from commit d9eeb028a23a66b78b08538a5ebf6052499ec24f)

* fix(opencode): fence legacy TUI viewers before creator attribution

Use the execution host's existing admission policy on the physical TUI
pane and launch token before rewriting to a session creator or mutating
the launch-token cache. Preserve live viewers, frozen server stamps and
OpenCode 2 behavior.

Add HTTP regressions for retired panes, closed tabs, replaced tokens and
relay retirement, plus current-launch, alias and compatibility controls.

Follow-up to #22838 and @werlang's original #22847; preserves the original
intentional creator attribution without copying that PR's source.

* fix(opencode): preserve current plugin exports in legacy TUI wrapper

Keep the current server, setup, id and other default export entries when
adding the legacy TUI entry. Cover their preservation and seed the ACL
fixtures with the separate installed TUI/config bytes.

This completes the current-main port of the legacy identity replacement
for issue #22838, carrying @werlang's original PR #22847 contribution.

* fix(opencode): retry timed-out SSH plugin updates (#24666)

Preserve bounded retry behavior and the current-main status-envelope fields.

Original-PR: #24124
Reviewed-source: 103144f9c5

Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(opencode): keep Go credentials private and resolve backend keys (#24615)

Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.

Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021

Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237


Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c

* Skip store notifications when refreshed work items are unchanged (#24530)

An identical forced refresh previously returned an empty Zustand patch, creating a new root state and notifying every subscriber. It now returns the current state after renewing the existing freshness timestamp. Real row or metadata changes still publish once.

* Read project activity once while sorting the sidebar (#24549)

Reuse the existing pure recent rank once per actual entry tuple within each synchronous sort; discard the lookup after sorting.

* Skip impossible link and tag matches in docs search results (#24646)

* Skip impossible HTML matches when formatting docs search results

After the existing link-removal phase, skip the unchanged HTML regex only when the current string has no closing delimiter.

* Skip impossible link and tag matches in docs search results

Skip the five unchanged link regexes when their protected-code input lacks ]; skip the unchanged HTML regex when its post-link input lacks >.

* Decode complete transcript lines without copying their bytes (#24546)

Decode each single owned Buffer synchronously; preserve concatenation for multipart records and remove a redundant tail array copy.

* Clear unused speech worker timers after errors and exit (#24924)

Call the existing idle timer cleanup when private current-worker state is cleared; keep current-worker guards, transcript/error order, deliberate warmth and stop deadline unchanged.

* Reuse documentation search excerpts during result navigation (#24574)

Memoize the existing pure excerpt renderer by complete raw text for the current result array, retaining original rendering as a miss fallback.

* Append subagent handoffs without recopying their message list (#24672)

Append each message reference to its private call-local scope list; retain the final merge and existing ordering.

* Cancel plugin retry timers when their fetch is replaced (#24700)

Reuse the existing timer clear block immediately after the fetch generation advances; preserve live retries and all state publications.

* Avoid rebuilding visible file paths for single-row selection (#24743)

Skip the visible path array only for keyboard replacement selection; the existing replacement branch never reads that order.

* Reuse prepared reads while searching saved agent conversations (#24535)

Use schema-complete session columns to enable the existing bounded statement cache without caching result rows.

* Reuse discovered mobile chunks during static traversal (#24774)

Iterate the existing live visited Set in FIFO discovery order instead of maintaining a second shifted queue; both actual callers own untouched esbuild JSON metadata.

* Skip unused image-size calculations in mobile web browser requests (#24941)

Use the existing mobile density budget only for mobile view; pass the existing constant to the existing assembler for web/default mode, where that argument is discarded. No cache, policy, request or native path changes.

* Skip diff analysis after a result has been discarded (#24711)

Move the unchanged pure render-limit calculation after both existing generation and section-token rejection fences.

* Skip late browser grab toasts after their surface closes (#24727)

Reuse the existing mounted-owner ref at the toast presentation entry while preserving all admitted extraction/screenshot and clipboard completion.

* Classify native chat waiting messages once (#24771)

Use the existing native filter traversal to populate a fresh waiting array, keeping the original predicate, policy, projection and output ordering while classifying each actual private slot once.

* Skip discarded Kanban pruning for an empty selection (#24782)

Guard only the existing open pruning branch when both its private selected Set and nullable anchor are empty; retain all original pre-guard projections and nonempty/anchor-only/public-helper behavior.

* Index discovered test files once during shard selection validation (#24532)

Verified selection plans previously validated each selected filename with a scan of all discovered files. One per-call Set now handles membership checks. Exact source/discovery checks, nonempty selection, fallback to all tests, shard balancing and manifests remain intact.

* Clear the watchdog benchmark heartbeat when CPU sampling fails (#24802)

Move the existing initial CPU observation and histogram enable inside the existing try/finally so their failures clear the sampler's owned heartbeat, preserving successful operation order and original errors.

* Reuse the parsed notification when leasing a push delivery (#24645)

* Reuse the parsed notification when leasing a push delivery

Pass the notification already parsed for the dismissal check into the existing private delivery builder.

* Reuse the parsed notification when leasing a push delivery

Pass the notification already parsed for the dismissal check into the existing private delivery builder.

* Update README downloads badge

* Let retired-cache GC observations settle across the existing six-turn budget (#24967)

* Collect test-selection evidence when full unit tests fail (#24955)

* Collect advisory unit-selection evidence from failed full runs

* Trigger checks after retargeting the evidence fix to main

* Check each project repository once while filtering mobile cards (#24540)

Reuse exact raw source/slug matching decisions within one project filter call, preserving the matcher, membership, negative matches, output order and identity.

* Skip renderer callbacks after their request has ended (#24631)

Reuse the existing pending-request identity check before starting its deferred renderer callback.

* Decode single-piece saved-session tails without copying them (#24632)

Decode the existing owned Buffer directly when the EOF tail has one piece; preserve the existing concatenation for multiple pieces.

* Avoid irrelevant scroll-action searches in Mac snapshots (#24731)

Check the current pure action name before searching for vertical scroll actions in the same immutable array.

* Stop copying every retained browser page before attachment (#24553)

Find the first page/generation match directly in the existing insertion-ordered Map instead of copying all page references into an array.

* Reuse event byte sizes when relay watchers notify several clients (#24558)

Store the existing grouped numeric byte-size array on the already call-local watcher batch sizing object, for reuse by later chunking clients.

* Stop building unused import candidates in the test planner (#24611)

Replace the existing ordered extension map/find with a loop that forms each candidate only when its existing lookup is reached.

* docs(terminal): explain local macOS/Linux shell startup files

Document the actual login/interactive shell startup-file order and existing shell setup behavior.

Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(editor): recognize Ruby task and configuration files

Add Ruby task/configuration filenames to the existing generated language associations.

Co-authored-by: ggbdpq <ggbdpq@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* Speed up large Markdown Find and render oversized tables (#24948)

* Speed up large Markdown Find and render oversized tables

* Poll table preview geometry outside hidden renderers

* Preserve Markdown navigation through refresh and tab restoration

* Confirm Markdown restoration when refreshed content is ready

* Trace table refresh positions and update Unicode search reference

* Recognize queued measurement scrolls before restoring Markdown anchors

* Rebuild Markdown Find ranges after renderer components change

* fix(source-control): generate clean OpenCode messages locally and over SSH (#24613)

* fix(source-control): generate clean OpenCode answers on local and SSH hosts

Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.

Original-commit: 64bb15e3f2
fix(source-control): generate clean OpenCode answers on local and SSH hosts

Use configured models and JSON answer/error events, preserve run-first arguments, and handle the precise v2 variant rejection. Hydrate SSH execution-host PATH through the existing bounded login environment resolver before direct spawning.

Credits: andy-murr (PR #5197 SSH environment intent) and coelho-doti (PR #13065 argument-order intent).

Original-commit: 1b60ec5d11
fix(source-control): retry inline OpenCode model and variant options

Original-commit: 98fdecc7a3
Preserve OpenCode named errors without a data message

Restacked-from: 98fdecc7a3
Restacked-onto: f7b1f9d8be

* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)

* fix(ci): run mobile typechecks without concurrent dependency refresh

* test(ci): check effective Linux E2E package list

* test(ci): preserve the mobile production compiler barrier

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* test(terminal): restore the live fish fixture prerequisites (#24947)

A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.

Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.

* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)

Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.

Original-commit: f707cde14a
fix(shortcuts): defer TUI editing chords in terminal-first mode

Original-commit: 0c6348e49e
docs(shortcuts): describe deferred preview terminal chords

Original-commit: be62c1b6c6
Align worktree history shortcut metadata with terminal conflict policy

Restacked-from: be62c1b6c6
Restacked-onto: f7b1f9d8be

* Register supervised Qoder China and Qwen Code (#24616)

* Add Qoder session history and search with real CLI coverage

* Allow the real Qoder marker file to end with a newline

* Keep Qoder tool output out of history previews and search

* Keep Qoder search pages readable by older clients

* Verify persisted Qoder history after a real generated and resumed task

* Negotiate Qoder filters before searching an older execution host

* Combine search client imports for the CI plugin gate

* Keep the relay search oracle aligned with legacy agent filtering

* Register supervised Qoder China and Qwen lifecycle integration

* Cover Qoder China mobile assets and mixed-host resume gates

* Verify Qoder provider tags against the older released wire parser

* Verify China and Qwen keep independent Windows hook scripts

* Verify Qoder registrations against the installed older Windows release

* test(qoder): align search capability contracts and pin old-host fencing

* fix(qoder): rank exact picker identities and command aliases first

* test(qoder): preserve the regional CLI shared icon expectation

Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.

* fix(qoder): align China catalog entry with fallback order

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)

* Select lookup automatically for the measured hosted root-install profile

* Record hosted automatic-mode cold cache publication proof

* fix: bound remote generation setup and honor OpenCode option terminators

Count execution-host profile resolution inside the existing request deadline, cancel its waiter promptly, and pass only the remaining time to the child. Shared bounded profile probes keep their existing cache lifetime; the SSH transport margin is unchanged.

Read OpenCode output format from active final-argv options before -- so literal prompt arguments cannot select the JSON finalizer.

Fresh exact-source controls reproduce nine failures before; 139 related checks pass after, including primary/fallback delays, deadline boundaries, cancellation, parser metadata, and SSH lanes. Node typecheck and strict changed-file lint pass.

* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)

* feat(antigravity): bridge IDE history into new CLI conversations

* fix(antigravity): preserve fresh-launch model and environment for IDE references

* fix(antigravity): forward IDE history opt-in through desktop IPC

* fix(antigravity): rebuild remote IDE reference startup on its host

* fix(antigravity): register IDE continuation action labels

* fix(antigravity): confine IDE references and bound metadata reads

* fix(antigravity): localize IDE continuation badges

* Preserve scanner service cache assertions and refresh Antigravity opening metadata

* Preserve Antigravity opening joins and target folder runtime authority

* fix(opencode): retry timed-out SSH plugin updates (#24666)

Preserve bounded retry behavior and the current-main status-envelope fields.

Original-PR: #24124
Reviewed-source: 103144f9c5

Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(opencode): keep Go credentials private and resolve backend keys (#24615)

Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.

Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021

Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237


Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c

* fix(opencode): enforce deadline through executable startup

Keep synchronous Windows PATH resolution and child startup within the existing request budget.

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(persistence): preserve projectGroupOrder on restart for flat folder-scan groups

Retain saved project ranks when the project remains in its folder-scan group.

Co-authored-by: lurunzi <lurunzi@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(runtime): reclaim temp files orphaned by interrupted mobile store writes

Reuse the existing stale temporary-file cleanup policy when each mobile store opens.

Co-authored-by: LDH1103 <ldh517525@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(terminal): show actionable copy for missing folder workspace paths

Show the existing folder-path failure with concrete recovery instructions.

Co-authored-by: Wayn_Liu <wayntingliu@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* docs: reference local orca.yaml and .worktreeinclude

Document the existing workspace configuration, include/copy rules and sharing behavior.

Co-authored-by: Neil <neil@stably.ai>

* fix(orchestration): allow model selection for OMP workers

Pass an explicitly requested model through the existing OMP worker launch catalog.

Co-authored-by: Neil <neil@stably.ai>

* fix(cli): preserve primitive success results in JSON output

Check for an object before inspecting screenshot fields so primitive success results remain printable.

Related: https://github.com/stablyai/orca/pull/14735

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: VXNCXNX <VXNCXNX@users.noreply.github.com>

* Show loaded file changes before deleting a workspace

Expose up to ten already loaded changed paths without altering deletion counts, hydration or authorization.

Related: https://github.com/stablyai/orca/pull/22778

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Michiel de Gooijer <mdgooijer@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(keybindings): record and match Option+digit shortcuts on macOS

Use the physical digit key for explicitly assigned Mac Option+digit shortcuts while retaining modifier checks.

Co-authored-by: marcuslannister <marcus@lannister.cc>
Co-authored-by: Neil <neil@stably.ai>

* fix(editor): don't throw closing a stale active file

Recover stale active-file selection within the owning workspace before choosing a surviving editor.

Related: https://github.com/stablyai/orca/pull/24715

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Neil <neil@stably.ai>

* Fix Project Settings targeting for multiple local checkouts

Carry the selected checkout identity into the existing project Settings action.

Co-authored-by: fsmeier <1506919+fsmeier@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>

* feat(documents): open CSV and TSV files from the OS

Extend existing OS document associations and delivery to CSV/TSV, preserving restoration and authorization.

Co-authored-by: Neil <neil@stably.ai>

* feat(cli): link GitHub and GitLab items on worktree create and set

Expose and validate the existing workspace link fields, preserving omitted values and provider identity checks.

Related: https://github.com/stablyai/orca/pull/22609

Co-authored-by: marco song <marco.song@mvlchain.io>
Co-authored-by: SongMarco <20613630+SongMarco@users.noreply.github.com>
Co-authored-by: Luca Critelli <lucacri@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* feat(sidebar): include folder workspaces in keyboard navigation

Use the rendered sidebar row order and host identity when cycling through folder and Git workspaces.

Related: https://github.com/stablyai/orca/pull/10555

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: JeongUk Park <jeongph.dev@gmail.com>

* fix(build): turn off MSBuild file tracking for Windows native rebuilds

Default Windows native rebuilds to TrackFileAccess=false while preserving explicit caller preferences.

Co-authored-by: B1nh M1nh <43268322+b1nhm1nh@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>

* Fix Ctrl+M in Linux and Windows terminals

Keep the Minimize menu item but disable its accelerator registration on Linux and Windows.

Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>

Related contribution: https://github.com/stablyai/orca/pull/24143

* fix(startup): read a nushell login PATH from $env.PATH

Use Nushell login command syntax and preserve the actual login PATH value.

Related: https://github.com/stablyai/orca/pull/22677

Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(cursor): run local hooks through sh for non-POSIX login shells

Keep POSIX hook syntax inside one quoted sh command so the login shell can invoke it safely.

Related: https://github.com/stablyai/orca/pull/22663

Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>

* Fix word wrap for both panes in side-by-side diffs

Forward wrapping to both diff panes through the existing editor option path and clean up listeners.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(monaco): highlight Svelte block closers inside markup

Return Svelte block closers to the existing markup tokenizer state.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(repos): keep active clone dialog open on outside clicks

Ignore accidental outside dismissal only while cloning; Escape, Close and Back still cancel.

Related: https://github.com/stablyai/orca/pull/24581, https://github.com/stablyai/orca/pull/23430

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>

* fix(editor): keep chat visible after closing Markdown tabs

Count remaining unified chat tabs before clearing workspace selection during editor close.

Related: https://github.com/stablyai/orca/pull/24273, https://github.com/stablyai/orca/pull/23760

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* Honor typed starting numbers in chat ordered lists

Forward the existing parsed ordered-list start attribute to both chat Markdown renderers.

Related: https://github.com/stablyai/orca/pull/19765

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Frederic Barthelemy <git@fbartho.com>

* Normalize base source once while checking changed-code diagnostics (#24557)

Reuse the exact existing moved-code matching algorithm with run-local lazy normalization of immutable base source blocks across diagnostics.

* Support real OpenCode sessions in native Chat (#24647)

* Use bounded OpenCode context for vault session continuation

OpenCode database and synthetic row paths are not text transcripts. Use the
vault preview or captured pane context, preserving actual transcript paths
containing a hash and supporting both OpenCode lanes and Windows paths.

Adapted the intent of #11859 and extended it to actual installed v2 vault rows.

Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>

* Read real OpenCode sessions in terminal-backed native Chat

Reuse the bounded AI Vault SQLite worker for v1 and v2 session pages and live updates. Keep terminal input as the real execution path and pace OpenCode Stop through its two-Escape interrupt.

Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>

* fix(opencode): publish approval cards for permission requests

* Send OpenCode native approval through its Enter selector

* Resolve mobile Chat readability for folder workspaces

* Bound OpenCode part batches and preserve v2 image attachments

* Prefer live migrated OpenCode sessions over legacy copies

* Consolidate mobile Chat eligibility test imports

* Consolidate OpenCode SQLite protocol type imports

* Update native chat settings contract for both OpenCode agents

* fix(native-chat): reconcile bounded OpenCode transcript reads

* fix(native-chat): dispatch OpenCode questions safely

* fix(native-chat): keep native discovery and transcript windows current

* feat(accounts): link standalone GLM Coding Plans (#24618)

* feat(accounts): link standalone GLM Coding Plans

Adapt the reviewed GLM accounts contribution to current main, retain Antigravity behavior, guard late credential results, expose storage protection, and redact quota errors.

Co-authored-by: Luchong <lu740528977@gmail.com>

* fix(accounts): retain GLM credential results during quota refresh

* fix(accounts): make GLM credential editing desktop-only

* fix(accounts): mirror the host GLM site in paired clients

* fix(accounts): report unknown GLM host details and split web settings tests

Apply the independently reviewed Accounts correction from697284a without the v2 adapter commits. Preserve the saved-key store and serialized write behavior.

* fix(zcode): ship required GLM account translation entries

* chore: record GLM reconciliation hook validation

* chore: validate installed GLM commit hooks

* test: complete GLM account fixtures and web API inventory

---------

Co-authored-by: Luchong <lu740528977@gmail.com>

* fix(native-chat): route transcript requests through shared SQLite worker

* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)

* fix(ci): run mobile typechecks without concurrent dependency refresh

* test(ci): check effective Linux E2E package list

* test(ci): preserve the mobile production compiler barrier

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* test(terminal): restore the live fish fixture prerequisites (#24947)

A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.

Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.

* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)

Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.

Original-commit: f707cde14a
fix(shortcuts): defer TUI editing chords in terminal-first mode

Original-commit: 0c6348e49e
docs(shortcuts): describe deferred preview terminal chords

Original-commit: be62c1b6c6
Align worktree history shortcut metadata with terminal conflict policy

Restacked-from: be62c1b6c6
Restacked-onto: f7b1f9d8be

* Register supervised Qoder China and Qwen Code (#24616)

* Add Qoder session history and search with real CLI coverage

* Allow the real Qoder marker file to end with a newline

* Keep Qoder tool output out of history previews and search

* Keep Qoder search pages readable by older clients

* Verify persisted Qoder history after a real generated and resumed task

* Negotiate Qoder filters before searching an older execution host

* Combine search client imports for the CI plugin gate

* Keep the relay search oracle aligned with legacy agent filtering

* Register supervised Qoder China and Qwen lifecycle integration

* Cover Qoder China mobile assets and mixed-host resume gates

* Verify Qoder provider tags against the older released wire parser

* Verify China and Qwen keep independent Windows hook scripts

* Verify Qoder registrations against the installed older Windows release

* test(qoder): align search capability contracts and pin old-host fencing

* fix(qoder): rank exact picker identities and command aliases first

* test(qoder): preserve the regional CLI shared icon expectation

Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.

* fix(qoder): align China catalog entry with fallback order

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)

* Select lookup automatically for the measured hosted root-install profile

* Record hosted automatic-mode cold cache publication proof

* fix(native-chat): keep OpenCode history usable at read limits

Continue past failed database probes while preserving discovery cancellation.
Verify rows displaced by a capped tail before deciding whether to replace history.
Represent oversized v1/v2 rows with the existing omission text and stable cursors.

* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)

* feat(antigravity): bridge IDE history into new CLI conversations

* fix(antigravity): preserve fresh-launch model and environment for IDE references

* fix(antigravity): forward IDE history opt-in through desktop IPC

* fix(antigravity): rebuild remote IDE reference startup on its host

* fix(antigravity): register IDE continuation action labels

* fix(antigravity): confine IDE references and bound metadata reads

* fix(antigravity): localize IDE continuation badges

* Preserve scanner service cache assertions and refresh Antigravity opening metadata

* Preserve Antigravity opening joins and target folder runtime authority

* fix(opencode): retry timed-out SSH plugin updates (#24666)

Preserve bounded retry behavior and the current-main status-envelope fields.

Original-PR: #24124
Reviewed-source: 103144f9c5

Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(opencode): keep Go credentials private and resolve backend keys (#24615)

Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.

Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021

Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237


Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c

---------

Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
Co-authored-by: Luchong <lu740528977@gmail.com>
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* Drain removal fixture jobs before resetting and deleting their records (#24977)

* Reuse measured Electron preparation for current Terminal Perf refs (#24968)

* feat(jcode): add Jcode as a supported TUI agent with managed hooks

Ports PR #10521 onto current main: agent catalog, managed hook service,
agent-status listener, session resume, AI Vault parser, per-pane daemon
isolation, and Source Control AI support.

Co-authored-by: Neil <neil@stably.ai>

* feat(jcode): evidence-backed status pipeline (turn_start, live pre_tool, questions)

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): register vault fixture, dynamic model discovery, script refresher

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): keep finished-turn detail so completion notifications fire

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): show the prompt in agent rows instead of jcode's repainting title

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): pre-warm the per-pane daemon so a cold runtime dir cannot time out

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): stop the OSC color skip from crashing every pane connect

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* refactor(jcode): fold three reverse-scan copies into one, reuse the shared hook POST

The jcode journal reader, the Claude transcript reader and the Command Code
transcript reader each carried their own copy of the same reverse chunked line
scan; they now share one tested helper. jcode's managed hook script drops its
hand-rolled curl for buildPosixAgentHookPostCommand, which also gains it the
raw-JSON transport and the --noproxy guard the bespoke copy was missing.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): narrow dynamic reads with predicates, pin the Windows hook shape

CI's anti-slop audit rejects Reflect.get: parse dynamic input into a named type
instead. Adds Windows script-shape tests too, since Windows is the platform this
change could not be exercised on directly.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* test(jcode): measure the gate against the synchronous path, not the clock

The absolute 1s bound was the flake CI shard 3/8 hit: it is tight enough to catch
a synchronous gate on an idle laptop and too tight on a loaded runner. Measuring
the same POST both ways on the same machine makes the claim a ratio, which is what
the test is actually about. Mutation-checked: a synchronous gate reads 6120ms
against a 1517ms observer.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* test(jcode): drop the wall-clock gate assertion

The bound was machine-speed sensitive and flaked on CI shard 3/8 at 1525ms. The
structural assertions (detached gate branch, foreground observer branch) and the
no-hang stdin drain cover the same contract without a timer.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): address CodeRabbit review on #22539

- Windows posted no payload at all: the shared builder reads `payload@-` from
  stdin, which the gate has already drained and observer hooks never receive, so
  every Windows event was dropped. Write the env var to a temp file and pipe it.
- The daemon pre-warm never fired for daemon-host spawns, which is the default
  local path; it now runs there too, and from the final env so the daemon gets the
  hook port and token.
- A failed runtime-dir mkdir took down every local terminal, jcode or not.
- removeJcodeManagedHooks matched the raw line, so a user hook whose comment
  mentioned the managed script was deleted; matching on Windows never worked.
- A managed entry left by a copied home or a platform switch is now repointed
  instead of being reported as user-owned forever.
- The OSC colour skip only checked launchAgent, so a command- or telemetry-named
  jcode pane still leaked the reply into its composer.
- `['hooks']` and a commented scalar are recognised, instead of appending a
  second [hooks] table that makes jcode reject the whole config.
- A failed tool's error is marked as tool output rather than agent prose.
- The vault keeps a session's stored name, counts its tokens, and skips
  background_task and [Scheduled task] turns.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): read the journal once per turn, key prompts by byte offset

The jcode prompt reader ran a synchronous bounded file scan plus a JSON
parse on every hook event. jcode blocks on pre_tool, so a turn that ran
four tools charged the user eight scans of latency it did not need — the
prompt cannot change inside a turn.

- Cache the journal read per pane, refreshed on the turn boundary that
  can change it. The cache holds the whole evidence record, since
  hasExplicitUserPrompt needs the transcript-evidence flag and not just
  the text, and it joins the existing pane-scoped lifecycle (close,
  rename, reset) rather than living in a module singleton.
- Key a journal prompt by its absolute byte offset instead of its
  region-local line index. The backward scan windows the file from EOF,
  so appending shifted every boundary and reminted the key for a prompt
  that never moved; a repeated turn_end then slipped past the same-hash
  dedupe as a second done event with duplicate telemetry.
- Pin the platform in the daemon pre-warm tests. The pre-warm is a no-op
  off POSIX, so the dedupe and retry cases would have passed vacuously
  on a Windows runner.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): quote the managed hook path, drop tools from patch prompts

Three fixes, all on paths this PR could not exercise locally.

The managed hook command was stored as a bare path. jcode tokenizes that
string shell-style before exec'ing it directly (parse_hook_command,
crates/jcode-terminal-launch/src/lib.rs): unquoted whitespace splits, and
every unquoted backslash is consumed as an escape. So on Windows
`C:\Users\me\.orca\agent-hooks\jcode-hook.cmd` reached exec as
`C:Usersme.orcaagent-hooksjcode-hook.cmd` and no hook fired at all, and a
POSIX home with a space split into two arguments. Store the path
single-quoted (verbatim, backslashes included), falling back to double
quotes for a path containing a single quote. Existing bare entries are
already repointed by the stale-key path, and getStatus accepts both forms
so the repair is not reported as a user-owned hook. The quoting helper was
previously dead code that only tests called; the three production sites
now use it. isJcodeManagedCommand also normalizes separators, since a
`/`-only needle never matched a Windows entry.

Commit-message generation feeds a staged patch to `jcode run` as the
prompt — attacker-influenced text — while jcode's default profile exposes
shell, read, write, and MCP. Pass `--tool-profile none`, which resolves to
an empty allowed-tool set in jcode's config (base_allowed_tools), matching
the read-only posture claude (plan) and codex (read-only) already take.

docs/reference/jcode-hook-events.md was never actually in this PR: the
repo ignores docs/** and tracks reference docs by allow-list only, so the
captured-payload evidence four source comments point at was silently
dropped. Allow-list it.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* test(jcode): pin the tool-profile flag in the generation plan too

The argv assertion lives in two places; --tool-profile none only landed in
one, so the plan test still expected the unrestricted argv.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(text-generation): refuse an oversized argv prompt on Linux too

The pre-spawn size guard only ran on Windows. Linux caps a single argv
entry at MAX_ARG_STRLEN (32 pages, 128 KiB on a 4-KiB-page host) and
execve fails with E2BIG past it, so an agent that delivers the whole
prompt as one argument — jcode, and the other argv-delivery agents —
failed on a large staged diff with an error the user could not act on.

The cap is per-argument and in bytes, which is why it is not the Windows
line budget: 40k chars trips Windows and is nowhere near the Linux limit,
so folding them together would have refused prompts Linux runs fine.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): register in main's remote-installer guard, drop our duplicate

Rebasing onto 853 commits of main surfaced two things the earlier branch
had hidden.

main already owns a guard for the issue-#7253 bug class
(`remote-hook-service-registry-coverage.test.ts`). This branch had added a
second, near-identical one — a parallel implementation of a test that
already existed, which is what AGENTS.md's reuse rule is about. Deleted
ours and registered jcode in main's, which is the one that has kept pace
with every agent added since.

Also fixes a missing separator in the mobile icon map. `pnpm tc` does not
cover `mobile/`, so only the session-route closure suite caught it.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): decompose the four files jcode pushed over max-lines

Adding an agent tipped four modules past their line budget. AGENTS.md
forbids a `max-lines` disable or a per-file bump, so each is split on a
real seam rather than silenced:

- agent-catalog.tsx keeps `AgentIcon`, which 70+ files import, and the
  rows move out. The rows alone exceed the 300-line budget a `.ts` file
  gets, so they follow the primary/secondary split this repo already uses
  for commit-message agent specs.
- getAgentResumeArgv -> agent-resume-argv.ts, re-exported so the 18 call
  sites keep one import path.
- isDiscoverableSessionFile/pathSegments -> session-file-discovery.ts.
- remoteCodexSources -> remote-session-scanner-codex-sources.ts; Codex is
  the one remote agent with two CODEX_HOME roots.

Also:
- Records the readiness-census baseline jcode now needs. main added that
  gate while this branch was out; the fixture is the recorded 74-case
  matrix, not a hand-written one.
- Restores two entries a rebase resolution silently dropped from
  config/tsconfig.cli.json (gitlab/project-ref-parser,
  startup/shell-path-probe). Nothing to do with jcode; losing them was a
  conflict-resolution mistake on this branch.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* feat(native-chat): open structured chats on the paired Orca server that owns the workspace (#24205)

* fix(native-chat): a host admits structured sessions by client capability, not its own chat setting

A host's experimentalStructuredNativeChat decided whether any paired client could reach
agentSession.* at all, and whether session.tabs.* showed it structured tabs. That setting is the
host user's own launch preference: whether a new agent opens as a chat or a terminal is decided by
whoever launches it. Using it as admission control meant a client whose own preference was
"structured chat" was refused on a host whose preference was "terminal", and chats opened while
the setting was on were withheld from mobile once it was turned off.

The gate now asks one thing: did the client advertise agent-session.structured.v1 (in-process
callers negotiate nothing and are always admitted). Tab projection and restore follow the same
rule. With the setting no longer gating anything, the separate cleanup gate (close, cancel,
unsubscribe, release), which existed only so those kept working after the setting was switched
off, is identical to the main gate and is folded into it. The settings listener that republished
tabs when the setting changed is removed, since projection no longer depends on it.

The host setting still picks the default for launches that start on the host itself
(agent.launch from mobile, orchestration worker-start).

* fix(native-chat): the desktop declares structured chat support to paired hosts

The desktop renderer advertised agent-session.structured.v1 (and the Claude, turn-item and
background-task capabilities that go with it) to its own main process but not to a paired Orca
server. The server therefore refused every agentSession.* call from the desktop and stripped
structured chat tabs out of the tab list it published to it, so a structured chat running on a
paired server never appeared on the desktop, even though the renderer already mirrors a host's
agent-session tabs and drives each one against the server that owns its workspace.

The same renderer reads structured chats on either host, so the remote Electron list now carries
the same structured-session capabilities as the local one, and the capability test pins that
nothing is advertised only locally.

* feat(native-chat): open structured chats on the paired server that owns the workspace

With the structured-chat default on, an agent launched in a workspace that lives on a paired Orca
server always opened as a terminal (or the terminal-backed chat view). Three things kept it off
the structured path: the launch check refused every host but this machine, a remote workspace was
handed to the host-published terminal path before the structured route was even considered, and
the structured launch pipeline sent create and every follow-up call to this machine's runtime.

A workspace's owning runtime is fixed, so the pipeline now derives it from the workspace instead
of assuming this machine (structured-agent-session-owner.ts, the same derivation the chat pane
already uses to read a session). The launch intent carries that target; the pre-create support
check, create, the publication check and fence read, the launch prompt send, held option picks,
the "focus this chat" marker, the placeholder tab's host, and tab close/purge all use it.

The launch check now accepts a paired server and asks that server's own capabilities (read from
the status the client already cached for it) rather than this machine's. An SSH workspace stays
terminal-backed: no Orca runtime runs there. The host still answers createSupport before
anything is created, so an older server that refuses shows the failure in the chat tab.

A chat the user closed before its create landed is now also retired on the paired server when it
publishes, as the local sync already does. Orchestration workers placed on another runtime are
unchanged: federation creates terminal agents only.

* fix(native-chat): negotiate client-chosen launch mode so released phones and old servers keep terminals

Hosts advertise agent-session.structured.client-launch-mode.v1: they admit
structured sessions by client capability alone. A remote client that does
not advertise it (phones released before agent.launch) asks createSupport
to pick the launch mode, so the host keeps answering that with its own
setting, exactly as before. Cleanup methods keep their own named gate so a
future admission condition cannot make close or cancel refusable.

* refactor(runtime): keep the Electron client capability list in its own module

protocol-version.ts is at its line budget; the list is what the desktop
advertises to paired hosts, not the host's own contract.

* fix(native-chat): the desktop declares it picks each launch mode itself

Paired hosts and the desktop's own main process then answer createSupport
by the workspace rather than by their own chat setting.

* fix(native-chat): pin each structured chat to the host it was launched on

- Route: a paired server opens a chat only when it advertises the
  client-chosen launch mode; an older server keeps its terminal. Its
  capabilities come from the store's host status, not the compatibility
  cache that is empty after boot or reconnect.
- A launch command override is this machine's: the route applies it only
  locally, and a host's createSupport refuses on its own override.
- The owning host is resolved once, from the same value the route used,
  and carried on the launch intent, its persisted record (legacy records
  load as local), the provisional tab and every mirrored chat tab. Close,
  purge, retry and reload read it instead of re-deriving it from a
  worktree id two hosts can share; an owner that cannot be named refuses.
- Cancellation tombstones record their host: only that host's
  authoritative inventory retires one, restored cleanup closes it there,
  and a paired host's tombstone expires after 30 days if it never answers.
- A paired server's frame settles launches it published, as the local
  inventory already does for this machine.

* fix(native-chat): a paired server that declines a chat opens its terminal instead

createSupport only reads, so both of its non-answers are settled before
anything is created:
- A paired server that answers it cannot run the chat (a WSL repo, a
  Claude account mismatch, its own launch command override) closes the
  chat tab and opens the terminal the route would have chosen, with a
  notice saying why. This machine's own decline stays a failed chat.
- A host that could not be asked closes the chat tab and leaves one
  failure toast, instead of a lingering "could not confirm" chat.

* test(native-chat): a provisional chat carries its launch's host and hands pre-create failures on

* chore(native-chat): justify the two type assertions this change's lines touch

* test(native-chat): state why each staged test fixture is cast

* fix(native-chat): chats that already exist keep showing whatever the chat setting says

The structured chat setting decides only what new agents open as. With it
off, this machine's structured chats used to be hidden while the host,
which no longer reads the setting, still reported them to the workspace
activation gate, so a workspace holding only a chat opened empty. The
local chat mirror and its startup restore now run whatever the setting
says, the continue-after-restart offer follows the chats that exist, and
the setting's copy says it applies to new agents.

* fix(native-chat): the browser client keeps its host terminal on paired servers

A browser client whose own preferences turn structured chat on took the
structured route for every paired-server workspace, but its handshake
never says it reads structured sessions, so the server refused the chat
and the user got a failed chat tab where a host terminal used to open.
The route for a paired host now also asks what this client advertises to
it: the desktop's list does, the browser client's does not. Its handshake
list is now a named constant the route reads, so the two cannot drift.

The chat setting's copy now says it runs on paired Orca servers too;
WSL and SSH hosts still use terminal chat.

* fix(native-chat): a retried launch a paired server declines opens its terminal too

A launch restored after a reload settles only through its Retry, so a
declining paired server left a failed chat there while a first launch got
the server's terminal and a notice. The chat's Retry now hands the same
pre-create failures to the same replacement, carrying the prompt the
launch had staged.

* refactor(native-chat): a paired host's cancelled-chat record ends on its 30-day TTL

The paired census re-read a host's whole inventory after every
authoritative frame to retire tombstones, and a tombstone restored after
a reload needed a second such frame, so in practice it retired nothing.
A tombstone guards a random session id and is inert once stale; the chat
is already closed on its host whenever a frame shows it. The census, its
trigger in the mirror layer and its cleanup are removed; the owner-scoped
tombstones, close-on-sight, the TTL and publication marking from frames
stay.

* fix(native-chat): a chat's pane and status read from the host recorded on its tab

The chat pane and its sidebar status still derived the host from the
workspace id, which two hosts can share; a paired chat in a non-active
same-id workspace was read from this machine. Both now read the owner
stamped on the tab, as close, purge and publication already do.

* fix(native-chat): "Resume in chat" follows the terminal resume's host rule

Agent Session History offered "Resume in chat" for a conversation
recorded on this machine into a paired server's workspace, where its
transcript does not exist. A chat now resumes a conversation only on the
host that recorded it, as the terminal resume does, and that host is the
one asked whether it can resume history.

* fix(native-chat): the chat setting says older paired servers keep terminal chat

* test(native-chat): pin that a host advertises the client-chosen launch mode

* fix(native-chat): mirror this machine's chats only where it holds them

Round 1 ran the local chat mirror for everyone so existing chats show
whatever the setting says. That gave every desktop a permanent
session-tabs listener, which turns on the runtime's phone replication
paths, plus two full session-tab censuses at startup, and made the
browser client mirror its remote host a second time.

The runtime now says whether it holds structured chats: its structured
host is built only when saved chats were restored at startup or a client
created one here, and it announces the moment one is built. The mirror,
the startup restore and the continue-after-restart offer run only when
the setting launches chats or the host holds some, and never in the
browser client. A chat a paired client creates here with the setting off
still appears at once. The chat behaviour settings show wherever chats
exist, and the setting's copy says it picks what new agents open as. The
toggle-off teardown this made dead is removed.

* test(native-chat): route a paired-server launch over the capability lists both sides really advertise

* test(native-chat): record install listeners without a cast

* fix(native-chat): a paired server admits a chat before any of it exists here

The desktop opened a paired server's chat tab, launch record, queued
prompt and focus intent before asking the server, so a "no" needed a
replacement that undid and redid all of it, and every piece it missed
was a bug: the workspace deselected, the caller told "failed" while a
terminal ran its prompt, the caller's arguments and other queued prompts
lost, and a create whose reply was lost treated as never sent.

A paired launch now asks the server first and commits nothing until it
answers. Admitted opens the chat as before. Declined runs the caller's
own launch as the server's terminal, with the existing notice (a resume
fails instead, having no terminal equivalent). Unreachable opens nothing
and names the server in one toast. The new-tab launcher reports the
host's surface for paired workspaces, as it did before paired chats,
with the prompt delivery of whichever surface got the prompt. The
replacement and its error classes are gone, and the probe inside a
launch is back to its old meaning: a "no" is a failed chat with Retry,
and no answer leaves "Could not confirm" with Retry and the prompt kept,
here as on this machine.

* fix(native-chat): mirror this machine's chats only once it holds one, not once its host is built

Session history, resume preparation, terminal resume commands and replay-safe phone launches all
build the structured host for users who never had a chat, which turned on the chat mirror and the
structured-only settings rows until the next restart. The signal is now derived from the host's
records (or a records file still owed its import) and pushed when the first chat is restored or
created. A throwing listener no longer fails the install that fired it.

* fix(native-chat): a fork's reveal never seeds a terminal beside the surface the launcher opens

Forking into a paired-server workspace revealed it as if nothing would open there, so the reveal
created a blank host terminal beside the forked chat (and beside a forked agent terminal on main).
The launcher always opens the fork's surface itself, so the reveal now says so for every surface,
as the fix-checks launch already does.

* fix(native-chat): a declined direct launch keeps the caller's CLI args; an unreachable resume toasts once

When a paired server declines a "Fix checks" chat in a new workspace, the terminal that opens
instead now carries the recipe's saved CLI arguments, launch platform and launch source, as the
terminal route did. "Resume in chat" to a server that cannot be reached showed the admission's
"Could not reach" toast and the vault's generic one; the admission marks its failure notified and
the vault adds nothing.

* fix(native-chat): a declined background create opens its terminal without switching workspaces

Since #23974 a worktree create the user moved away from must not pull them onto the new
workspace. When a paired server declined that create's chat, the fallback terminal opened as a new
agent tab, whose host create selects the workspace. The create now opens its own agent terminal the
way main's background branch does: in place from the request's startup plan (so its CLI args carry),
without selecting the workspace. A create the user is still watching keeps the new-tab fallback.

* test(native-chat): name the launch's host in main's new outbox fence test

Main's new staging-failure test calls settleStructuredAgentLaunchPrompt without the target this PR
made required; it is a local launch, as in the sibling tests.

* fix(native-chat): a paired server's new chat shows no model until the server reports the one it started

A chat on a paired server starts with the server's saved model and options, but the picker showed
this desktop's saved selection (or the catalog default) until the server reported a model, and a
pick made in that window was remembered on the server under that guessed model. A paired launch
now carries no desktop seed, and until the server reports its model the picker names no model and
takes no picks. Local chats are unchanged.

* test(native-chat): seed the paired repo without a cast

The repo literal already satisfies Repo, so the changed-lines cast gate has nothing to excuse.

* feat(native-chat): createSupport reports the saved selection a new chat on this host starts with

A chat on a paired server starts with the server's saved model and options, which the desktop could
not read, so its picker showed a guess. createSupport's answer, which the desktop already waits for
before a paired launch, now also carries that seed as a new optional field (older clients ignore it).
Create and createSupport read it through one resolver so they cannot drift.

* fix(native-chat): a paired server's new chat shows the selection the server will start it with

The paired server now names its saved model and options in the admission answer the desktop
already waits for. That seed goes into the launch intent and its persisted record, so the picker
shows the server's model at once, stays pickable like a local chat, and remembers picks on the
server under that model; a reload shows the same. The locked picker remains only for a server too
old to name a seed.

Also moves host admission and launch-outcome tracking into their own modules: the latest main
merge left structured-agent-session-launch.ts over the max-lines limit.

* test(native-chat): expect the launch intent's new seed argument in exact-call assertions

* refactor(protocol): move the Electron remote client capability list into its own module

Merging main left protocol-version.ts one line over the max-lines limit on this branch. The list of
capabilities the desktop advertises to a paired host moves, unchanged, into
electron-remote-runtime-client-capabilities.ts, the module the next PR in the stack already uses
for it; importers point there.

* fix(native-chat): a paired chat with no saved server model is pickable; Retry shows the server's current seed

A server whose user never saved a chat model sends no seed, and the desktop showed a locked,
model-only picker for it, although that is the common case: no server that can admit a paired chat
predates the seed field. Such a chat now behaves like a local chat with no saved model: the CLI
default, pickable. The lock and its snapshot helper are gone.

Retry kept the first admission's seed while the create probe, which already runs on every attempt,
reported the server's current one and dropped it. The probe's seed now replaces a paired launch's
seed and the picker's, so a retried chat shows what its create will run.

* test(cross-version): stub the launch seed resolver createSupport now reads

* test(protocol): pin the desktop capability divergence against what a paired server receives

Every paired transport sends the shared remote base plus the Electron list, so the
divergence test now compares that union with the renderer's local list instead of
the declared Electron list. A capability added only to the shared base can no
longer slip past it. The two base-only capabilities it surfaced are recorded:
skills.install-result.v2 has no local caller; the authoritative-inventory label is
read by the local tabs sync but dropped by main, and is marked unsettled.

The turn-item and both background-task-stop capabilities were already sent through
the shared base, so the Electron list no longer repeats them. The wire set is
unchanged; this PR's real change on the wire is structured.v1, the Claude
structured capability and the client launch-mode capability.

* fix(native-chat): the desktop tells its own host it picks each launch mode, so retrying an existing chat works with the setting off

* docs(native-chat): name the real exit for the released-phone createSupport rule

* fix(native-chat): the route reads the capabilities a paired host actually receives

The renderer decided whether a paired host would admit a chat from the desktop's Electron list, but
every desktop transport sends that list plus the shared remote base. They agreed only because the
route's checks happened to sit in both. The route input is now built with the same
remoteRuntimeClientCapabilities the transports use (the browser client already sends its list as is),
and a test pins each against the real handshake.

* test(cross-version): a released client still gets the host-setting createSupport answer; a launch-mode client gets supported plus the seed

* test(native-chat): let main's child-records test resolve each chat's owner

Main's new test mocks worktree-runtime-owner with only the runtime environment id, but the status
projection in this PR also resolves each structured chat's owner from the worktree. The mock keeps
the module's real exports and overrides only what the test pins.

* fix(deps): take #24204's lockfile that the merge reverted

* test(native-chat): let the Codex child-approval e2e unit test resolve each chat's owner

Its worktree-runtime-owner mock exported only the runtime environment id, but this PR's status
projection also resolves each structured chat's owner from the worktree. The mock now keeps the
module's real exports and overrides only that id, as structured-child-records-switch does.

* test(native-chat): move the close-race launch cases into their own file

Merging main added launch tests on both sides and took structured-agent-session-launch.test.ts past
the 800-line limit. The three cases where a tab close races a launch move to
structured-agent-session-launch-close-race.test.ts, with the same setup the other split launch
suites copy.

* feat(cli): add --unread/--read to orca worktree set

Pass validated read/unread flags through the existing workspace metadata update.

Contributor attribution correction: the earlier Ctrl+M squash message placed its related link after the co-author footer, so GitHub did not recognize all contributors. Preserve that credit here for https://github.com/stablyai/orca/pull/24100 and its related contribution https://github.com/stablyai/orca/pull/24143 .

Related CLI link validation retained from https://github.com/stablyai/orca/pull/20036 .

Co-authored-by: Raz Shlomo <12373339+razshlomo@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>

* feat(cli): configure external worktree visibility per repo

Pass show/hide/inherit to the existing repository visibility update.

Related: https://github.com/stablyai/orca/pull/23025

Preserves CLI link validation from https://github.com/stablyai/orca/pull/20036 and read controls from https://github.com/stablyai/orca/pull/22714 .

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: KAPUIST <thsxornjs12@gmail.com>

* perf(git): reuse queries and stop canceled catalog scans (#24923)

* perf(git): reuse queries and stop canceled catalog scans

* refactor(git): check relay filesystem error codes

* Clarify OpenCode generation comment after real legacy catalog probe

* test(e2e): require exact Linux package tokens (#24995)

* Allow cold node-pty setup on Windows ARM CI (#24997)

Keep a ten-minute bound only for node-pty rebuilt on Windows ARM CI hosts; all other node-gyp calls retain five minutes. Cold setup in two completed ARM jobs left compilation less than a minute.

---------

Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Brynn Bendixen <brynnbendixen@gmail.com>
Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
Co-authored-by: 小七 <ggbdpq@gmail.com>
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: lurunzi <lurunzi@gmail.com>
Co-authored-by: Dongho Lee <126236161+LDH1103@users.noreply.github.com>
Co-authored-by: LDH1103 <ldh517525@gmail.com>
Co-authored-by: Wayn_Liu <115852642+Waynting@users.noreply.github.com>
Co-authored-by: Wayn_Liu <wayntingliu@gmail.com>
Co-authored-by: VXNCXNX <VXNCXNX@users.noreply.github.com>
Co-authored-by: Michiel de Gooijer <mdgooijer@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Marcus <70784566+marcuslannister@users.noreply.github.com>
Co-authored-by: marcuslannister <marcus@lannister.cc>
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Florian Schwarzmeier <1506919+fsmeier@users.noreply.github.com>
Co-authored-by: SongMarco <snubflow@gmail.com>
Co-authored-by: marco song <marco.song@mvlchain.io>
Co-authored-by: SongMarco <20613630+SongMarco@users.noreply.github.com>
Co-authored-by: Luca Critelli <lucacri@gmail.com>
Co-authored-by: JeongUk Park <jeongph.dev@gmail.com>
Co-authored-by: B1nh M1nh <43268322+b1nhm1nh@users.noreply.github.com>
Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Wooseong Kim <2222333+innocarpe@users.noreply.github.com>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>
Co-authored-by: Frederic Barthelemy <git@fbartho.com>
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
Co-authored-by: Luchong <lu740528977@gmail.com>
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: razshlomo <razshlomo@users.noreply.github.com>
Co-authored-by: Raz Shlomo <12373339+razshlomo@users.noreply.github.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Co-authored-by: KAPUIST <thsxornjs12@gmail.com>
2026-10-03 06:42:52 -07:00
c1d403a47b feat(cli): link GitHub and GitLab items on worktree create and set
Expose and validate the existing workspace link fields, preserving omitted values and provider identity checks.

Related: https://github.com/stablyai/orca/pull/22609

Co-authored-by: marco song <marco.song@mvlchain.io>
Co-authored-by: SongMarco <20613630+SongMarco@users.noreply.github.com>
Co-authored-by: Luca Critelli <lucacri@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
2026-10-03 00:51:19 -07:00
Jinwoo Hong 40e60b7385 refactor(ai-vault): delete the unused session-scanner worker thread (#24607)
* refactor(ai-vault): delete the unused session-scanner worker thread

Production always scans through the forked session-scanner service process;
the worker thread was reachable only under NODE_ENV=test or the
undocumented ORCA_AI_VAULT_SERVICE_PROCESS=0 switch, and nothing fell back
to it on service failure. Remove the thread (spawn, client, protocol, entry,
tests), its build entry, knip and plain-node-guard listings, and the
backend switch, so session-scanner-background always routes to the service.

- Move the scan options type to the service protocol as
  AiVaultServiceScanOptions.
- Tests now mock session-scanner-service-spawn, the seam production calls.
- Repoint the hot-path listing reliability gate from the worker-client test
  to the service-client test, which covers the same bounded-queue,
  cancellation, and fault-restart properties for the real executor.

STA-9122

* test(ai-vault): cover the service's Claude-vs-OMP subagent lister choice

Runs the real service entry and subagent reader, replacing only the two
per-agent listers, so a swapped lister choice fails.

STA-9122
2026-10-02 19:58:33 -04:00
Jinwoo Hong f8a31edd6f refactor(cursor): move the desktop-login read onto the shared foreign SQLite reader worker (#24603)
* refactor(sqlite): rename the OpenCode SQLite worker entry to foreign-sqlite-reader (STA-9122)

The worker thread that reads OpenCode's database off the main thread is about
to read other apps' databases too, so its entry is renamed to what it is:
src/main/foreign-sqlite-readers/foreign-sqlite-reader-entry.ts, built as
out/main/foreign-sqlite-reader-entry.js.

Why now: #24572 fixed the Cursor focus freeze with a second, dedicated
worker. Rather than grow one worker per foreign app, the next commit moves
Cursor onto this entry and deletes that worker. This commit is the rename
only; #24572's cursor-desktop-profile-worker-entry lines stay until then.

It moves out of ai-vault/ into a new foreign-sqlite-readers/ module because
it will no longer be session-scanner code; the module will own the readers,
their dispatch, protocol and main-process client.

The entry still routes only OpenCode kinds in this commit. The OpenCode
dispatch, protocol and process entry stay in ai-vault/ and stay OpenCode-only,
because the SSH/WSL relay reader bundles them (build-relay.mjs).

Every reference is updated: electron.vite.config.ts input key, knip entry,
the plain-node entry guard and its test, the asarUnpack list (the scanner
service still spawns this entry under ELECTRON_RUN_AS_NODE), and the
electron-builder test that reads the filename. The filename and the
beside-or-one-up (Rollup chunks) lookup now live in
foreign-sqlite-reader-entry-path.ts, which the OpenCode spawn reuses, plus an
Electron-main resolver that uses the packaged app.asar path.

* fix(cursor): move the desktop-login read from its dedicated worker onto the foreign SQLite reader (STA-9122)

#24572 fixed the Cursor focus freeze (#24360) with a dedicated worker
(rate-limits/cursor-desktop-profile-worker*.ts). Orca already runs OpenCode's
database reads on a worker, and more foreign-app SQLite reads are coming, so
keeping one worker per app means one entry, build input, asarUnpack line, knip
entry and guard line each. This keeps one pattern instead: Cursor's
state.vscdb read runs on the shared foreign SQLite reader entry, and the
dedicated worker, its entry and its config lines are deleted.

What moves:
- The read itself is a pure cursorProfile reader in
  foreign-sqlite-readers/readers/ (was rate-limits/cursor-desktop-state-db.ts),
  run only on the worker. A separate dispatch owns the new kinds and refuses
  an unknown kind. The entry routes OpenCode kinds to the untouched OpenCode
  dispatch, so the relay's OpenCode reader stays byte-identical.
- ForeignSqliteReaderClient gives each reader its own WorkerThreadRequestQueue
  lane (own lazily started, idle-torn-down thread; one shared factory) with
  in-flight dedupe per database path. Any failure resolves to the reader's
  existing failure value and never falls back to the main thread.

Kept from #24572, so every reader gets them:
- Await worker retirement before respawning. Worker.terminate() cannot
  interrupt a native SQLite call (e.g. a WAL-index rebuild), so the old
  thread lives on until that call returns; respawning at once stacked a new
  thread on the same work for every timed-out read (#24572 measured three
  live workers). This belongs in the shared host, which fire-and-forgot
  terminate(): LazyWorkerThreadHost now takes awaitRetirement and refuses to
  spawn until the terminated worker settles, and the queue fails calls closed
  meanwhile. Opt-in, because pure-JS clients (session scanner abort, port
  scan) respawn right after an abort. A rejected terminate() also ends
  retirement, so it cannot latch the reader off (raised in #24572's review).
- 10 s Cursor timeout, 2 consecutive deaths, a queue cap of 8.
- #24572's worker tests, rewritten against the shared client: responsive
  caller plus coalesced probes, unavailable worker without path leaks,
  stalled-worker recovery, no respawn before retirement, dispose settles.

Tests: reader, dispatch, client (timeout, 10 s default, crash, malformed,
unavailable without a main-thread read, dedupe, queue cap, own thread per
reader), queue retirement (stalled and rejected terminate), import boundary,
and an event-loop test reading a ~50 MB WAL with no -shm on a real worker.

* test(sqlite): walk the reader import boundary with the shared source-tree scan (STA-9122)

* fix(sqlite): key reader dedupe on a caller-supplied key, not the path alone (STA-9122)
2026-10-02 19:00:41 -04:00
Neil 1aa0860f7e Keep large Markdown previews responsive (#24880)
* Keep large Markdown previews responsive

* Fix large preview review navigation and Find budgets

* Initialize preview scroll caches once and check viewport visibility

* Restore large previews after loaded rows are measured

* Refresh loaded Markdown rows after viewport changes

* Keep Markdown revisions visible and reuse bounded search text
2026-10-02 15:22:03 -07:00
Neil 0a81211994 fix(cursor): read desktop login outside the main thread (#24572)
* fix(cursor): read desktop login outside the main thread

* fix(cursor): await native worker retirement before respawning
2026-10-02 03:48:33 -07:00
Brennan Benson cfa43e7eab fix(codex): opening a terminal no longer strips Codex hooks from the real ~/.codex (#23552)
* fix(codex): a real-home restore leaves a file alone once someone else changed it

Orca writes ~/.codex/hooks.json (and a trust rebase writes config.toml), then
runs a Codex trust session for up to 10 s, then restores the original bytes if
the session fails. The restore wrote unconditionally, so a save that landed
during the session, from the user or another Orca, was silently reverted.

Each restore now compares first: it writes the original back only while the
file still holds the generation Orca's mutation left, and otherwise logs and
leaves it alone. This covers the real-home install and opt-out sweep
(restoreRealHomeHooksJson), the legacy sweep's hooks restore, and config.toml
rollback (restoreCodexTrustConfig).

For hooks.json the generation is the exact bytes Orca wrote. For a config.toml
that a trust rebase changed it is the file as the rebase left it. When Codex
itself wrote config.toml inside the session that just failed, Orca never knew
those bytes, so that rollback compares against the file as the session settled.
The next commit keeps other Orca instances out of that window; a user edit made
during such a session can still be rolled back.

* fix(codex): serialize real-home Codex writes across Orca instances

Every Orca on one HOME (a dev and a packaged app, or an offline CLI) writes the
same ~/.codex/hooks.json, config.toml and ~/.orca/agent-hooks/codex-hook.sh.
The per-file lane that orders capture, mutate and restore was in-process only,
so another instance could write inside this one's restore window, or undo it.

The lane for the user's real config.toml now also holds the existing
crash-safe managed-hook install lock (~/.orca/managed-hook-install.lock, the
one relay installers take for the same home). It is taken only by the
outermost acquire, because the lock file is not reentrant and grants and trust
rebases nest inside an install. Managed-home installs, the real-home install
and opt-out sweep, and the legacy sweep all enter through it. Compare-and-swap
on restore stays as the backstop.

A lock that cannot be taken within its 10 s wait fails that install, which is
already best effort: launch prep logs it, and the real-home lane falls back to
the managed lane until its retry.

* fix(codex): opening a terminal no longer strips the shared Codex entry from ~/.codex

Every Orca instance on one HOME writes the same status-hook entry into the
user's ~/.codex/hooks.json, with its trust in config.toml. Launch prep runs on
every pane spawn, and under a managed Codex account it ran the legacy system
sweep. That sweep matched Orca entries by script file name, so it removed the
current shared entry and the trust blocks the grant ledger recorded. On a live
laptop hooks.json went 4139 -> 18 bytes about 150 ms before a new pane opened.
With hooks off, the real-home lane's launch prep swept the same way.

Now nothing automatic removes the current entry or its trust:
- The legacy sweep removes only an enumerated list of retired command forms
  that no build writes any more (#1019's double-quoted form, #1536's
  exec-guarded form, and Windows' per-userData bare path), plus their trust.
- ensureRealHomeCodexHookState with hooks off writes nothing; that covers
  launch prep, session resume and startup.
- Only the user's explicit opt-out (codexHookService.remove()) strips the entry
  and its ledger-recorded trust from the real home.
- The sweep-suppression gate existed only to stop the sweep from deleting the
  current entry, so it is deleted with its main-process wiring.

Startup with hooks off already skipped the real-home install; with this change
the first pane's launch prep with hooks off also leaves ~/.codex untouched.

* fix(codex): a pane's prepare-codex only repairs a home its own HOME's app installed

On macOS a pane starts through login(1), so it gets the user's real HOME even
when its Orca app runs with another one. The pane's `codex()` preflight
installed hooks in the CLI process with that real HOME: it rewrote
~/.orca/agent-hooks/codex-hook.sh, promoted trust into the real config.toml,
and wrote the real HOME's script path into the app's managed home.

The preflight now acts only when the managed home's hooks already run this
process's own shared script, which proves the app that installed them shares
its HOME. Otherwise it writes nothing; the app installed the home at spawn.

Why not a no-op: the preflight was added (#14326) because trust can go stale
between opening a pane and typing `codex`, for example in a pane that survives
an app update, and Codex then stops in hook review. For a same-HOME pane it
still repairs that. Why keep promotion: the install drops runtime trust the
system config does not back, so skipping promotion would delete approvals the
user gave inside Orca-launched Codex.

* test(agent-hooks): await every installer in the refresher coverage test

The test fired each managed installer without awaiting it and read
~/.orca/agent-hooks straight after. Codex's install now takes the
cross-process real-home lock before it writes its script, so the script
landed after the read. Await the installers, and stub Codex's trust sessions
so the awaited install cannot start a real `codex app-server`.

* fix(codex): retire the two real-home command forms the list missed

The real-home lane wrote two Codex hook forms into ~/.codex that no build
writes any more and that the enumerated retired list did not name:
- POSIX, #9501 until #10885: the file-guarded form draining with a bare `cat`.
- Windows, #9501 until #10221 took Windows off the real-home lane: the encoded
  PowerShell launcher for a non-cmd-safe script path.

The file-name sweep removed both before; the enumerated sweep left them in
place, trusted, still passing the script's exit status to Codex. Both now
match as frozen literals.

Also corrects the startup ordering comment: the real-home install runs first
so its in-slot upgrade lands before the managed install's sweep retires the
prior command; nothing re-arms a legacy sweep any more.

* fix(codex): take the real-home lock only when a write is needed

The previous commit made every entry to the real-home config lane take the
cross-process lock. That lane runs on every pane spawn and every typed
`codex` preflight, so the steady state paid an owner probe (a `ps` spawn on
macOS) and could wait up to 10 s behind another instance's trust session,
even though it wrote nothing.

Each real-home writer now compares the desired state with the files on disk
first, without the lock. Only when a write is needed does it take the lock,
re-read and recheck, then write:
- real-home install: the planned hooks.json, the shared script and the
  ledger-recorded grant are compared; the locked path re-plans from disk.
- legacy sweep: locks only when a retired entry is present; the sweep re-reads.
- approval promotion: locks only when there is something to promote; the
  promotions are recomputed under the lock.
- the shared ~/.orca/agent-hooks script: locks only when its bytes differ.
The explicit opt-out always takes the lock. The lock is reentrant through
async context, since grants and rebases nest inside an install, so the
config-lane option the previous commit added is removed.

* fix(codex): a shared script without its exec bit is not the steady state

The compare-first check matched the shared ~/.orca/agent-hooks script on bytes
alone. writeManagedScript also restores 0755 on every call, and the POSIX hook
guard skips a script that is not executable, so a script whose mode was lost
(a dotfiles restore, a plain copy) now stayed that way: every Codex hook
drained stdin and reported nothing until an app restart refreshed the script.

The check now also requires the mode the writer sets, so that case takes the
lock and the write path repairs it.

* test(codex): the retired encoded launcher never matches today's shared one

The shared encoded Windows launcher is still current for other agents, so the
comment claiming today's launcher is never encoded was wrong. What keeps the
retired matcher off it is the exact payload: since #14825 the shared launcher
prefixes its payload and drops -ExecutionPolicy Bypass. Pin that with a case.

* fix(codex): the pane step recognises its own script under a home path with an apostrophe

The same-HOME check looked for the script path wrapped in bare single quotes,
but both hook writers escape an apostrophe inside the quotes. A home such as
C:\Users\O'Brien never matched, so the pane-step repair never ran there.

* fix(codex): the trust-RPC escape hatch still keeps the real home off its lane

The no-write check reported a recorded grant as current, so with
ORCA_DISABLE_CODEX_TRUST_RPC set the real-home lane stayed in use. The grant
itself refuses before reading its ledger; the check now does the same.

* fix(codex): the shared script write no longer waits on the real-home lock

The write is atomic and skips identical bytes; waiting behind another
instance's trust session could only fail a pane's managed-home install.

* fix(codex): an in-Orca approval survives a launch that cannot get the real-home lock

The install drops runtime trust the system config does not back, so a
promotion skipped for want of the lock lost the approval for good. It
now writes unlocked, as it did before the lock existed.

* refactor(codex): take the cross-process real-home lock back out

The lock fixed no observed failure. The three that were observed each have
their own fix in this series: the legacy sweep matches only frozen retired
command forms, hooks-off launch prep writes nothing, and a pane's
prepare-codex repairs only a home its own HOME's app installed. The lock
instead brought its own defects: a steady-state spawn waiting behind another
instance's trust session, a compare-first split to avoid that, a script
write and an approval promotion that could fail for want of the lock.

Removed, with their tests: the real-home write lock and its async-context
reentrancy, the plan/compare split that kept it off steady-state spawns, the
compare-first legacy sweep, the locked approval promotion and its unlocked
fallback, the compare-first shared script write (writeManagedScript already
skips identical bytes and restores the exec bit), and the CLI tsconfig
entries the lock pulled in.

Kept: the retired-forms matcher, the hooks-off no-op, removal only on an
explicit opt-out, the pane own-script check, and the compare-and-swap
rollbacks. Every instance now writes identical bytes idempotently.

* fix(codex): an opt-out that cannot read hooks.json keeps Orca's trust and ledger

The opt-out swept the real-home entry, then dropped Orca's ledger-proven trust
whenever a ledger existed, even when the sweep could not read hooks.json. The
entry could still be there, now untrusted, and the ledger that proves ownership
was gone for the retry. Drop that trust only after a sweep that read the file.

* refactor(agent-hooks): one predicate for whether an agent's status hooks are on

"Global switch on and this agent not turned off" was spelled out separately
in the startup controls, the settings reconcile, the retained-home
reconcile, the WSL preflight RPC, the CLI preflight and the OpenCode plugin
selection. They now share one function, in a module light enough for the
CLI's per-launch Codex preflight to load. The PTY spawn env derives the
Codex flag from the switch and opt-out list it already carries, the same way
it does for OpenCode and Pi, instead of receiving a second copy.

* fix(codex): launch and resume prep honour Codex's per-agent hook opt-out

Turning Codex off in the per-agent hook settings removes Orca's Codex hook
entry, but launch prep and session resume read only the global hooks switch,
so the next Codex launch or resume wrote the entry straight back into the
real ~/.codex or the account's home. Both now read the per-agent predicate,
which the PTY spawn env and startup already honoured.

* fix(codex): turning Codex off per agent clears the real ~/.codex entry

While the real-home lane owns ~/.codex/hooks.json, the legacy system-home
sweep stands down. That gate read only the global switch, so turning Codex
off per agent ran remove() with the sweep still suppressed and left Orca's
entry in the real ~/.codex. The gate now reads the per-agent predicate, the
same as turning every hook off.

* test(codex): cover the system ~/.codex sweep gate for Codex turned off

The gate that lets the legacy system-home sweep run was an inline closure in
startup, so reverting it to the global switch left CI green. It is now a
pure function beside the gate it feeds, with a table test and a remove()
test on a seeded ~/.codex: turning Codex off strips Orca's entry and keeps
user hooks; with Codex on the entry stays.

* fix(cli): keep the agent-status hooks predicate loadable by the packaged CLI

The CLI's prepare-codex handler imported the predicate from src/main, but
the Electron build rebuilds out/main from its declared entries only, so the
packaged `orca agent hooks` commands could not load it (package jobs and the
CLI bundle-parity test were red). The predicate reads only settings, so it
now lives in src/shared, which the CLI compiles itself.

* feat(codex): every Orca build writes one frozen Codex hook command

The Codex hook command was built from this build's wrapper, so two builds
on one HOME disagreed about the bytes of the shared ~/.codex entry and kept
rewriting it, with a Codex trust session each time.

The command is now fixed per form and carries its form number:
- POSIX: one command with no path in it. It runs the shared script only in
  an Orca pane with hooks on (pane key and hook port set), drains stdin
  everywhere else, and always exits 0. A branch for a per-build script root
  is written now and stays dormant until Orca sets ORCA_AGENT_HOOK_ROOT, so
  that change will not move these bytes.
- Windows: the bare forward-slash path to the shared .cmd, which runs under
  PowerShell 7 and 5.1, Codex's hook hosts. A profile path that is not one
  PowerShell token gets a plain PowerShell form with the same branches.

The literals live in the form module, so a change to the shared hook
constants cannot move them; goldens pin the bytes. Every form keeps
`agent-hooks/codex-hook.*` in plain text, so older builds still recognize it.

* fix(codex): one main-process owner adds the real-home entry; nothing restores files

Each Orca writer of ~/.codex decided what Orca's entry must be from its own
build and instance, then removed or reverted whatever differed: launch prep
rewrote any Orca-shaped entry to this build's command and stripped Orca
entries from events this build does not use, and a failed trust session
restored hooks.json and config.toml from snapshots. With several instances
and builds on one HOME, every disagreement became a deletion or a revert.

The main process is now the one writer, and its writes are add-only:
- A launch or resume adds Orca's frozen entry to an event that has none and
  leaves every Orca entry it finds, so a running older build is never fought.
- App start also converts an older Orca form to the frozen command, once, in
  its own slot: one hooks.json write (one .bak) and one trust grant per home.
- A newer form is never rewritten or appended beside, and Orca entries in
  events this build does not use are kept.
- After a failed trust grant, only an entry this call wrote that is still
  untrusted is withdrawn, putting back the handler it replaced. Both files
  are re-read, so a concurrent edit, or the identical entry another Orca
  trusted meanwhile, survives.

Deleted: the compare-and-swap hooks.json restore, the config.toml snapshot
restore after a grant session and after a user-trust re-key, and the
rollback module. A grant session writes trust only at Orca's own keys, and
every caller settles those keys itself. A failed re-key of moved user hooks
now keeps the write and reports it; Codex lists those hooks for review.

* fix(codex): the pane CLI asks the app to prepare its Codex home

`orca agent hooks prepare-codex` ran Codex's install inside the pane. That
process can have the real HOME (login(1)) and runs outside the app's
in-process queues, so it was a second writer of ~/.codex and ~/.orca beside
the app. A check that the home ran "its own script" guarded it.

The pane step now only asks the app, over the same kind of local RPC the WSL
pane step already uses (agentHooks.prepareCodexForPane). The app checks that
the pane's CODEX_HOME is one its own userData owns, reads its own hooks
setting, and installs on its own queue. An app that is not running, or is
too old to know the method, makes the step a no-op, as it is on WSL. The
own-script check and the CLI's settings read are gone, and the preflight
module leaves the CLI bundle.

* fix(codex): delete the pane step on native hosts

The previous commit had `orca agent hooks prepare-codex` ask the app to
prepare the pane's Codex home. The case it existed for (#14326, a pane that
survives an app update with stale hook trust) did not reproduce, and no other
desktop agent host writes agent config from a terminal or launch wrapper.

- Deleted: the agentHooks.prepareCodexForPane RPC method, its params and
  catalog entry, and prepareManagedCodexHomeBeforeShellLaunch with its module,
  tests and CLI build entry.
- `agent hooks prepare-codex` is a no-op on native hosts. It stays for one
  release so shell wrappers from older builds, which still call it, exit 0.
- WSL panes are unchanged: they still ask the app over
  agentHooks.prepareCodexForWslPane.

The shell wrappers and ORCA_CODEX_LAUNCH_PREFLIGHT stay, because WSL panes
use the same wrappers and variable (forwarded through WSLENV). A native pane
still starts the CLI once per `codex` it runs; skipping that is a follow-up.

* test(codex): a failed trust session keeps concurrent edits to both files

QA case 9 at host level, on a real file system in a temp HOME: Codex's trust
session fails after another writer saved hooks.json and config.toml.

- Both saves survive, and no Orca entry is left that Codex would list for
  review: this call's entry is withdrawn.
- A failed one-time conversion puts the older Orca entry back in its slot and
  keeps both saves.

Both tests fail on the previous head, which restored config.toml from a
snapshot and left the untrusted entries in hooks.json. Removing the
withdrawal turns both red.

* feat(codex): read whether an Orca entry's stored trust is still current

A Codex release that changes how it hashes a hook leaves Orca's stored trust
stale: the entry is present, but Codex lists it as modified. Checking only
whether the entry is missing cannot see that.

readOrcaEntryTrust sorts a present entry into four states:
- trusted: the stored hash is the current one;
- untrusted: there is no stored hash;
- stale: the stored hash is not the current one;
- disabled: the user turned the entry off.

The caller can pass Codex's current hash, for example one a grant recorded.
The failed-grant withdrawal now uses it, and also keeps an entry the user
turned off. Nothing re-grants on 'stale' yet.

* fix(codex): a slow Codex start retries on the next launch, never for minutes

On a loaded Mac a cold `codex app-server` took over 10 s (QA case 4). The
grant timed out, the entry was withdrawn, and a 5-minute cooldown in both the
grant and the real-home install then refused every retry.

- The native session deadline is 30 s, the same as WSL's.
- A timeout starts no cooldown in the grant or in the real-home install. The
  next launch retries. Other failures keep their cooldown.
- Launches that queue behind a slow session share one follow-up run, so a
  launch waits for at most two sessions, not one per earlier launch.

Tests: a 15 s cold start still grants and keeps the entry; after a timeout,
the next launch runs a session at once; four queued launches run two
sessions. Each is red on the previous head, and each mechanism was removed in
turn to confirm its test turns red.

* fix(codex): Orca's automatic writes never move a user hook

Codex keys a hook's trust by its position in hooks.json. App start's collapse
of Orca duplicates removed every Orca entry and appended one at the end. That
moved any user hook that followed a removed entry, so the write waited on a
session to re-key the moved hook's trust.

App start now:
- converts the first Orca entry that sits in a plain slot to the frozen
  command, in place;
- drops any other Orca entry only when that moves no user hook;
- keeps a duplicate that a user hook follows, and trusts every frozen copy,
  so none is listed for review;
- appends only when no frozen entry is left.

Tests check user positions and user trust blocks byte-for-byte for each
automatic write: add-missing (append), the one-time conversion (in place),
a trailing duplicate, a duplicate before a user hook, and older duplicates
normalized to one entry. The three collapse cases fail on the previous head.
Removing the position check, or the in-place conversion, turns its tests red.
Only the explicit opt-out still removes an entry that user hooks follow.

* fix(codex): removing an Orca entry never waits on a Codex session

Removing an Orca entry from ~/.codex/hooks.json moves every user hook behind
it up a slot, and Codex keys trust by slot. The retired-form sweep, the
opt-out and a failed-grant withdrawal all asked a `codex app-server` session
to list the old trust before writing, and to re-key it afterwards. A timeout
there threw before the write and latched a 5-minute cooldown, so a slow cold
start blocked the retired-form sweep at boot (QA case 4).

Each moved hook's [hooks.state] block now moves to its new key, body bytes
unchanged, straight after the hooks.json write. Codex hashes a hook's content,
not its position or its file path, so the moved block stays exactly as valid
as it was: a trusted hook stays trusted, an untrusted one stays untrusted, and
one the user turned off stays off. No removal waits on or depends on a
session. A failed config.toml write keeps the hooks write and logs.

Deleted: the inspect and repair sessions, their client, and their cooldown.
The generation guards on the hooks.json writes stay, for other processes.

Tests: the retired sweep removes the retired entry and carries the trust of
the user hook behind it while every Codex session times out (red on the
previous head); the opt-out carries an appended user hook's trust; the move
carries trusted, disabled and untrusted states byte for byte. Removing the
move turns all of them red.

* fix(codex): a Codex launch never waits on Codex's approval of Orca's entry

A launch on the real-home lane awaited Codex's trust grant for the entry it
had just added. A cold `codex app-server` on a loaded Mac took over 10 s, so
the launch could wait that long, and a failure then latched a 5-minute
cooldown.

- Codex's approval runs in the background, with a 30 s cold-start budget.
- A launch uses the real home only when the ledger shows trust is already
  current. Otherwise it goes to the managed home at once, and the next launch
  picks up the finished grant.
- A launch that arrives while a grant runs does no work and does not queue
  behind it.
- A resume into the real home has no managed home to fall back to. It waits
  for the grant, but no longer than the 10 s a launch always could.
- A background grant that times out starts no cooldown; the next launch
  retries. Any other failure backs off for 10 s instead of 5 minutes.
  Success is what the ledger remembers.
- A failed grant still withdraws only what that install added and is still
  unapproved. The log now says how many entries it took back and when the
  next try comes.

Managed-home grants keep their 10 s deadline and stay on launch prep, as
before; they fall back to Orca-computed trust.

Tests:
- A 15 s start: the launch returns in under a second on the managed home, a
  second launch starts no session, the grant lands in the background, and the
  next launch uses the real home.
- A timeout sets no cooldown, withdraws its adds and logs it.
- Another failure retries after 10 s, not before.
- A resume waits only as long as allowed.
- Case 9 checks the log line and the retry.

Making the launch await the grant, a 10 s budget, either timeout cooldown, and
a 5-minute backoff were each tried, and each turns its test red.

* fix(codex): move a hook's trust only when every stored key has the known shape

Orca now edits Codex's trust store directly when a removal moves a user
hook. Three safeguards keep that honest:

- Fail safe. If any [hooks.state] key in config.toml does not have the
  shape `<path>:<event>:<group>:<handler>`, nothing moves and Codex asks the
  user to review. That shape was checked unchanged from Codex 0.141 to 0.158.
- Targeted. The file is read immediately before the atomic rename, and only
  the moved keys' blocks change. Every other byte stays, and no snapshot is
  restored.
- Verbatim. Each block's body moves as Codex wrote it, including fields
  Orca does not know. No hash is ever computed, and a hook with no block
  gets none.

Tests:
- An unknown key shape stops every move.
- Everything except the moved block survives byte for byte, and the moved
  body keeps an unknown field.
- In case 9, a hook the user approved during the failed session keeps its
  approval when the withdrawal moves it, beside the concurrent project edit.

Removing the shape check, or writing a computed block instead of the stored
body, turns these tests red.

* refactor(codex): keep only the trust read the failed-grant withdrawal uses

A capture across Codex 0.141, 0.150 and 0.158, switching in all six
directions, showed Orca's entry keeps the same hash and stays trusted. A
Codex upgrade does not make its trust stale, so nothing needs to re-grant
on staleness.

readOrcaEntryTrust keeps the four states the withdrawal needs, but loses
the parameter that let a caller pass a different current hash, and the test
for a Codex that hashes differently.

* fix(codex): native panes no longer start the Orca CLI before each codex

The pane step is a no-op on native hosts, but native panes still carried
ORCA_CODEX_LAUNCH_PREFLIGHT, so every `codex` typed in a pane started the
Orca CLI for nothing. Only a packaged Windows build's WSL pane now gets the
variable; the app prepares every native Codex home itself.

The resolver loses the dev-launcher path and its userDataPath option, which
only native panes used.

Tests: a native macOS, Linux and Windows pane gets no preflight, packaged or
not, even with the bundled CLI present; a WSL pane still gets the verified
absolute launcher. Letting native panes through again turns them red.

* chore(cli): say when the native prepare-codex no-op can go

Native pane wrappers from builds up to v1.4.216 still call it. It can be
deleted once no supported build's wrapper does.

* test(codex): check the WSL launcher path instead of asserting it

* fix(codex): a launch no longer waits behind the background real-home approval

The background grant ran its whole codex app-server session inside the shared
~/.codex/config.toml lane, and on a cold host its session was also the shared
capability probe. A launch sent to the managed home then waited on both: the
managed install and the project-trust write queue on that lane, and the
managed install's own grant waited for the probe. On a cold app-server that
was up to 30 s per launch.

The lane was held across the session only to protect the retired
capture-and-restore. Codex writes its own records, so the lane is now taken
only around Orca's own pre-grant write. The background grant runs its session
without publishing it as the shared probe, and the whole grant is bounded by
its deadline, so a hang outside the session cannot leave the lane 'granting'.

* fix(codex): a failed re-grant no longer strips Codex's own approval of Orca's entries

Before each trust session, the grant deleted every Orca record whose hash
matched the one Orca computes. That exists because a managed home's fallback
writes Orca-computed trust under both Windows path-separator spellings, and
Codex rewrites only its own spelling, so the other copy would linger. On
failure the managed and WSL fallbacks write that trust back, and before this
fold a snapshot restore covered it.

The real ~/.codex has neither: Orca never writes computed trust there (the
real-home lane does not run on Windows at all), so a matching record there is
Codex's own approval. After a ledger miss (another Orca profile, a Codex
update, a lost ledger) and a failed session, nothing put it back, and every
Orca entry showed "Hooks need review".

The clear now runs only for homes whose fallback writes that trust.

* fix(codex): a real-home resume spawns only once Orca's entry is approved or withdrawn

A resume that must run in ~/.codex waited at most 10 s for the background
approval, then spawned anyway. On a cold app-server that left Codex beside an
unapproved Orca entry, so the resumed pane showed hook review.

The resume now waits for the grant to settle. Settled means Codex approved the
entry, or the grant failed and withdrew its own unapproved write; the grant's
deadline bounds the wait (30 s, the cold-start budget), and a failed approval
never fails the resume.

Why this over the alternatives:
- Spawning at 10 s keeps the review prompt this fold exists to remove.
- Withdrawing at 10 s from the resume races the still-running session: Codex
  can write the frozen entry's hash after the withdrawal, and for a converted
  entry that marks the older command Orca put back as modified.
- A resume cannot use the managed home: the session lives in ~/.codex.
So the only states that cannot race Codex are the grant's own settle. The cost
is a longer worst case on a cold app-server (up to the 30 s deadline, plus any
managed-home install that holds the config.toml lane); a warm approval takes
seconds, and an approved entry costs no wait.

* fix(codex): keep the 5-minute trust cooldown for launch-path grants

The fold shortened the host's trust-grant cooldown from 5 minutes to 10
seconds for every grant. That was meant for the background ~/.codex approval,
which blocks no launch. The managed-home and WSL grants run inline on the
launch path, so with a hung app-server every launch more than 10 s after the
last failure paid the full inline timeout again (10 s native, 30 s WSL).

Cooldowns are now kept per lane: inline grants keep 5 minutes, the background
grant retries after 10 s, and neither lane's failure cools the other down. A
success, or a proven-missing surface, still clears both. The real-home
install's own retries (an unreadable hooks.json, unknown keys) are back on the
5-minute interval they had before the fold.

The cooldown moves to its own module so the grant stays within the file limit.

* fix(codex): a failed grant withdraws the exact copy it wrote

The withdrawal re-found "this call's" entry by command, taking the first
frozen handler in the event. When app start converted a later slot while an
earlier frozen copy sat in a matcher group (which conversion skips), a failed
grant acted on that earlier copy: it put the older command into it, or skipped
it, and left the converted, unapproved copy in place.

Each write now records where its handler landed, after any duplicate drops,
and the withdrawal acts only on that slot. A copy that has since moved is left
alone; the next launch's grant retries it.

* fix(codex): the failed-grant withdrawal checks hooks.json is unchanged before writing

The install and the retired-form sweep both refuse to replace ~/.codex/hooks.json
if it changed since they read it. The withdrawal did not: a save landing
between its read and its atomic replace was lost. The window is small, since
the withdrawal is synchronous, but it now carries the same guard.

* refactor(codex): drop rationale left over from the snapshot restore; name the trust-move module for what it does

Comments on the config.toml lanes still justified them by a grant's
capture-and-restore window, which the fold deleted, and the trust-write
deadline still counted a grant session holding the lane. They now give the
reason that remains: Orca's own multi-step reads and writes, and managed-home
installs that hold the lane across their inline grant.

codex-user-hook-trust-rebase no longer rebases through Codex; it moves stored
trust records, so it is now codex-user-hook-trust-moves.

The grant test that pinned two sessions on one config.toml to run one at a
time is removed: its reason was an interleaved capture and restore. Callers
that write config.toml around a grant hold their own lane, which the nested
installer test still covers.

* build(cli): list the trust-grant cooldown module in the CLI program

The CLI's agent-hooks handler loads the hook controls, which reach the Codex
trust grant; the CLI project is composite, so every module in that graph must
be listed.

* docs(codex): say which Windows hosts each hook command form runs under

Codex runs a hook under the turn's shell (PowerShell 7 or 5.1 in every
captured session) and, with no single local turn shell, under %COMSPEC% /C.
The bare forward-slash path ran under all three in the Windows host census.
The PowerShell form used for a profile path with a space does not parse under
cmd.exe; no form valid in all three hosts has been run for such a path, so the
form stays and the gap is stated here and in the PR.

* test(codex): type the withdrawal seam without an assertion

* fix(codex): a real-home resume starts at once, trusting Orca's entries for that process

A resume that must run in ~/.codex waited for Codex's background approval of
Orca's newly written hook entry: up to 30-40 s on a cold app-server. That made
the user's resume wait on bookkeeping, and the alternatives (start at 10 s with
Codex's hook review showing, or withdraw the entry and race Codex's own write)
were worse.

Codex reads hook trust from its session-flag config layer as well as the user's
config.toml, merged per key, and has since hook trust shipped. So the resume no
longer waits. When Orca's own frozen entries in ~/.codex are untrusted (or hold
a stale hash), the resume command carries
`-c hooks.state={'<key>'={trusted_hash='<hash>'},...}` for exactly those entries:
the key under both the logical and the real path of ~/.codex (Codex keys an
explicit CODEX_HOME by its real path), and the hash of that entry's content, so
it can trust nothing else at that slot. The user's hooks are never included,
nothing is written, and the background approval still runs for later plain
`codex` launches. An approved entry adds nothing; a Codex known to lack hook
trust gets nothing.

One inline table, because Codex splits a `-c` key on every `.` and the key holds
`.codex/hooks.json`. TOML literal strings keep `"` out of Windows native-argument
quoting. The flag goes before `resume <id>`, quoted for the pane's shell (portable
Unix, PowerShell or cmd), in the launch command and in the setup-sequenced copy of
it; a cmd line whose path cmd would expand, or a key with an apostrophe, is left
unchanged. SSH and WSL resumes get no preparation, so no local path reaches them.

* Revert "fix(codex): a real-home resume starts at once, trusting Orca's entries for that process"

This reverts commit 1bd30651d6.

* fix(codex): a real-home resume starts at once, without waiting for approval

A resume into the real ~/.codex waited until the background approval settled,
up to its 30 s deadline on a cold app-server: bookkeeping for later launches
gating the resume the user asked for. It now starts at once. If the approval is
still running, that first resume can show Codex's hook review once; the
approval then lands and later resumes and plain codex launches are trusted.

Trusting Orca's entries per process was the alternative, but the resume command
is typed into the pane's shell, and hook settings stay out of typed commands.

* test(codex): read real-home hook groups with the installer's own type

* fix(codex): a background approval is bounded only by its session's own deadline

Review loop 2, L3. grantWithinDeadline raced a second 30 s timer against
the background approval. Loop 1 added it so that a hang upstream of the
session could not leave the lane 'granting' forever.

That hang cannot happen. The only caller is the native real-home grant
(its plan is always host 'native'; the real-home lane is off on Windows,
so WSL never reaches it). Everything before the session is synchronous
there: command resolution and binary stamp, the ledger read, the
state-db backfill check, the capability and cooldown checks, and
runUnshared awaits no shared probe. A synchronous hang would freeze the
main thread, which no timer can rescue. The session itself starts a kill
timer right after spawn (runCodexAppServerSession), with the same 30 s,
and it kills the app-server tree when it fires.

So the outer timer was a second copy of that bound. Because it started
first, it won by the spawn time. It then settled the lane and cleared
backgroundGrant while the app-server was still alive, and the next
launch could start a second concurrent session. It abandoned the
session rather than cancelling it. Deleted, not moved: the session's
own timer is the one bound, and it cancels.

Test: codex-real-home-slow-app-server.test.ts "runs one session at a
time, ended by its own deadline". The fake session starts its timer
after a simulated spawn, as the real one does. A launch at 30 s finds
the session still running and starts none; the lane settles when the
session times out. It replaces the "settles a grant that never answers"
test, whose never-answering session could not time out at all.

* fix(codex): a background approval's retry has one schedule, the real-home lane's

Review loop 2, L4. A non-timeout background failure set two 10 s
schedules for one failure: the real-home lane's installRetryAfterMs,
which gates ensure, and a `<host>#background` cooldown in the grant
module. ensure's gate always tripped first, so the second one was
consulted only after something reset the first (turning hooks off).
Then it answered 'retry-cached', which wrote the entry into
~/.codex/hooks.json only to withdraw it again: churn, not protection.

Background plans now neither start nor consult a grant-module cooldown.
The real-home lane (installRetryAfterMs) is the one source of truth for
when a background approval runs again, and its 10 s interval moves into
codex-real-home-background-grant.ts, the module that sets it. The
cooldown module is back to one host-keyed map for launch-path grants,
with the same 5-minute interval as main. A success or a proven-missing
surface from either lane still clears the host's cooldown.

Tests:
- codex-hook-trust-grant.test.ts "neither starts nor waits on a
  cooldown for a background grant": two failing background grants each
  run a session and leave no cooldown; an inline failure still cools
  down inline grants and not the background one.
- codex-real-home-slow-app-server.test.ts "has one retry schedule:
  turning hooks off and on after a failure retries at once": after a
  failed approval, hooks off then on runs a session and installs,
  instead of a retry-cached write-and-withdraw.

* fix(codex): hooks turned off and on during an approval re-add Orca's entry

Review loop 2, L1. ensure returned at once whenever a background
approval was running, whatever the lane. Turning hooks off during an
approval sets the lane to 'removed' (usable), so turning them back on
returned 'removed' without re-adding the entry. Launches in that window
spawned in ~/.codex with no Orca hook and got no status for their
lifetime, for up to 30 s, until the approval settled and a later launch
re-added it.

ensure now returns early only while the lane is 'granting', which is
what the early return exists for: a launch never waits on Codex's
approval and uses the managed home until it lands. Any other lane runs
the normal add-missing install.

That install can start a second approval while the first is still
running. Approvals are now chained, so Codex still runs one session at
a time, and a finished approval clears the handle only if it is still
the latest one (before, an older approval's finally could clear a newer
one's handle). The older approval's result is already dropped by the
lane generation check.

Test: codex-real-home-slow-app-server.test.ts "re-adds the entry when
hooks go off and on during an approval, one session at a time". While
the approval hangs: opt-out removes the entry; re-enable re-adds every
entry, keeps launches on the managed home, and starts no second
session; once Codex answers, the lane is installed and every entry is
approved.

* test(codex): a launch during the real-home approval shows what it waits on

Review loop 2, M2. The launch test's fake Codex failed every
managed-home session at once with ENOENT, so the managed home's own
approval was an instant "unsupported" fallback, and the test could not
show that a launch sent to the managed home still waits on that home's
inline approval when its ledger misses (first use, a Codex update, a
lost ledger), up to 10 s, as on main.

Now the managed-home session behaves like a real one:
- "settles on the managed home with its hooks and the project trust
  written": the managed app-server answers; two launches settle in
  under 2 s while the real-home approval hangs, and the second launch
  finds the managed approval in its ledger (one managed session).
- new "waits up to the managed home's own 10 s approval when that home
  is cold too": the managed session fails at its own deadline, as the
  real one does. The first launch is still pending at 9.999 s and
  settles on the managed home at 10 s; the request asked for 10 s. The
  next launch settles at once, because the failed inline approval cools
  down for 5 minutes.

No product change.

* refactor(codex): the managed and WSL installs own their pre-approval trust clear

Review loop 2, L7. Before a Codex approval session, a managed or WSL home
clears the approvals Orca itself computed, because on Windows its
fallback writes them under both path spellings and Codex's canonical key
may not overwrite the other one. The fallback writes them back if the
session fails. ~/.codex has no such fallback, so there the clear would
only delete Codex's own records (loop-1 H2). The grant module carried
this as a plan flag, fallbackWritesSelfComputedTrust, and took the
config.toml lane around the clear itself.

The reviewer proposed moving the clear into the two callers. A literal
move, clearing before the grant call, is NOT behaviour-neutral, so this
does not do that:
- The grant first checks its ledger, which compares the stored hash
  with the one Codex recorded. Codex's hash equals Orca's computed one
  (the premise of readOrcaEntryTrust), so a clear before that check
  deletes exactly the record the ledger proves. Every managed launch
  would then miss the ledger and run an inline session (up to 10 s).
- Checked, not inferred: with the clear moved before the call in the
  managed install, codex-launch-during-real-home-grant.test.ts "settles
  on the managed home..." fails (2 managed sessions instead of 1).
  Log: ~/orca-qa/codex-real-home-leak/fb6/l7-literal-move.log

What this does instead: each caller passes its clear as the grant's
`beforeSession` step, which the grant runs only when a session will
actually run (after a ledger miss, and not on a cooldown or cached
fallback), exactly where the flag ran it. So:
- the flag and its "never set for the real home" rule are gone; the
  real-home grant passes no step, so the grant module has no path left
  that deletes a trust record in ~/.codex;
- the grant module's own lane acquisition around the clear is gone. It
  was always a pass-through: both callers already hold that file's
  lane (the managed install holds the runtime and system lanes, the
  WSL install holds its config.toml lane) across the whole grant.

No behaviour change. The loop-1 probes still pass as fixed: trust-strip
prints every entry trusted after a failed re-grant, and lane-hold
prints managedInstall=settled projectTrust=settled.

Tests (codex-hook-trust-grant.test.ts):
- "removes equivalent Windows fallback keys before the RPC writes
  canonical trust" now passes the managed caller's step;
- new "runs the caller's pre-session step only when a session runs":
  the step runs once for a session and not on the ledger hit after it.

* chore(codex): comments stop describing a lock held across the session, or a rollback

Review loop 2, L6 comment sweep (comments and one test name only):
- codex-trust-config-concurrent-launch.test.ts: the test named "does not
  let a failing launch roll back a concurrent launch" said the per-file
  lane was the only thing left and that the doomed run's rollback must
  not resurrect the file. There is no lane across a session and no
  rollback now. Retargeted to what it covers: "leaves a concurrent
  grant's records in place when a sibling grant fails" (a restore would
  still turn it red).
- codex-trust-grant-ledger.ts: "a grant session blocks launch prep" is
  true only of inline grants; the background one still costs an
  app-server start. The drift clause no longer says "before the pane
  launches", which is false for the real home.
- agent-trust-write-deadline.ts: a stray hard wrap.
The install.ts:105 comment was fixed with L1. A sweep of src/main/codex,
src/main/startup, src/main/agent-hooks, the trust presets and the CLI
handlers for rollback, restore, rebase, capture/restore, and a lane held
across a grant or session found nothing else stale; the remaining "no
restore" comments state the current rule.

* fix(codex): a real-home resume waits for the one running approval, up to its 30 s limit

Review loop 2, M1; coordinator ruling. A resume into ~/.codex has no
managed home to fall back to. 5a737261d8 let it start at once beside an
Orca entry still awaiting Codex's approval. Codex's TUI then shows a
full-screen hook-review picker before the session and waits for keys:
"Trust all and continue" also trusts the user's own unreviewed hooks,
and "Continue without trusting" leaves that session with no Orca status
for its whole life, because Codex does not reload hooks when Orca's
approval lands later. Panes restored at app start after an update hit
it too, since the start-time conversion leaves every entry awaiting
approval.

The resume now waits, but only while Orca's entry in ~/.codex is
written and a grant is approving it (lane 'granting'). Every resume
waits on that same in-flight grant: ensure never starts a second one
while the lane is 'granting', so panes restored together share one
session. The bound is the grant's own session limit (30 s). The grant
settles only after Codex approved the entry, or after it withdrew its
own unapproved adds, so the resumed session starts either trusted or
with no Orca entry: never beside an unapproved one, and no picker. On
a withdrawal that session has no Orca status, as on main after its
10 s wait. A failed approval never fails the resume.

Tests (codex-launch-during-real-home-grant.test.ts):
- "waits for a warm approval, and spawns with the entries approved";
- "spawns at the approval session limit with Orca entries withdrawn"
  (fake timers: pending at 29.999 s, spawns at 30 s with no Orca entry);
- "makes panes restored together wait on one approval session" (three
  resumes, one session, all settle once it lands).
codex-launch-per-agent-hook-opt-out.test.ts: a resume into ~/.codex
awaits the approval; a resume into a managed account home does not.

* fix(codex): repeated background approval timeouts back off, growing to 5 minutes

Review loop 2, M3; coordinator ruling. A timeout of the ~/.codex
approval starts no cooldown, so the next launch retries at once. On a
host where codex app-server never starts within 30 s, every launch then
wrote Orca's entry into ~/.codex/hooks.json, withdrew it again, and
started another 30 s session, for the rest of the process: an unbounded
retry with no exit.

After 3 timeouts in a row the retry now waits 10 s, then 1 minute, then
5 minutes for every later one. The first two timeouts still retry on the
next launch, so a slow cold start is not punished. Any other outcome
ends the streak (a success, or any other failure, which keeps its own
10 s wait). The streak lives only in memory, so every app start begins
at zero and a slow boot can never latch.

Tests (codex-real-home-slow-app-server.test.ts):
- "backs off after three timeouts in a row, growing to 5 minutes, and a
  success resets it": the first two timeouts retry at once, then 10 s,
  1 min, 5 min, 5 min; after a success, a fresh approval gets two
  immediate retries again and a 10 s backoff after the third;
- "keeps trying after timeouts during a slow first start, once the app
  server answers": three timeouts, then the next attempt at 10 s
  installs.

* refactor(codex): one approval at a time, decided under the config.toml lane

The real-home check kept a lane label, a generation stamp, a promise chain of
ensures and a chain of approvals, and decided from the label at call time.
Concurrent resumes from any state other than 'granting' each started their own
approval (N x 30 s), a chained approval ran a plan an earlier failure had
withdrawn, a hooks-off check during an approval released a waiting resume beside
unapproved entries, and an app-start conversion during an approval was dropped.

Now each check is one step under the real config.toml lane: an approval in
flight answers 'approving' (unusable), hooks off answers 'removed', an open
retry window answers 'unavailable', and otherwise the unchanged install runs and
starts at most one approval. The approval settles under the lane: it withdraws
its own unapproved adds on failure, sets the retry, and derives the verdict from
the settings and the outcome, then runs an owed conversion. A resume waits only
while an approval runs and an unapproved Orca entry is on disk. The opt-out
sweep moves verbatim into its own module.

* fix(codex): only a success or app start resets the approval timeout streak

The ruling is that three timeouts in a row back off, and the count resets on
success and at app start. A non-timeout failure or an unexpected error also
reset it, so a host alternating those with timeouts never backed off.

* fix(codex): a Windows profile path the shells cannot carry bare runs through cmd.exe

The Windows hook command was the bare forward-slash script path, or, for a
profile path that is not one PowerShell word, a PowerShell script. That script
cannot parse under cmd.exe, which Codex uses when a session has no single local
turn shell, so such a profile got no status there.

A path of only letters, digits and _ . : / ~ - stays bare. Any other path,
including one with a space, & ^ $ ` ' ! ( ) or a non-ASCII character, is written
as cmd --% /d /c @"<path>", which ran under PowerShell 7, Windows PowerShell 5.1
and cmd.exe for each of those characters with a real Codex 0.158.0. The choice
depends only on the path, so every build on a machine writes the same bytes. A
machine holding the earlier PowerShell spelling converts it once at app start.

* build(cli): list the real-home hook sweep module in the CLI program

* fix(codex): the Windows cmd spelling names the system cmd.exe and turns off delayed expansion

A profile path the shells cannot carry bare was written as
cmd --% /d /c @"<path>". Under Codex's cmd.exe host the outer cmd.exe resolves
a bare `cmd` from the hook's working directory first, so a repo holding
cmd.bat (or .cmd, .com, .exe) at the session cwd would run on every hook event.
And with delayed expansion turned on in the registry, a `!` in the path was
dropped.

The spelling is now <SystemRoot>/System32/cmd.exe --% /d /v:off /c @"<path>",
unquoted (PowerShell reads a quoted first token as an expression) and with
forward slashes. The Windows directory comes from %SystemRoot% when written,
else from the directory above %ComSpec%'s System32, so both give the same bytes;
if neither is a drive-absolute path it can spell unquoted, it is C:/Windows,
which is still absolute. The bytes stay a pure function of the profile path and
that directory, so every build on a machine writes the same command. Safe
profile paths keep the bare path. Older Orca forms, including the bare-cmd
spelling, convert once; the new spelling is never swept as retired.

* refactor(codex): an approval's settle runs no deferred conversion

An app-start conversion that arrived while an approval ran was remembered and
run by that approval's settle. The settle then rewrote an older entry in place,
unapproved, and started a second approval inside the same wait that releases
every resume, so a resume could start beside an entry Codex would put up for
review.

That path could not happen: the only conversion caller is app start, and it is
the process's first check, so no approval can be running when it arrives. The
deferral and the settle's second check are deleted. A conversion that met an
approval would now be skipped until the next start, and the test for this case
pins that the settle writes nothing new and runs one session.

* fix(codex): an approval's settle keeps a failed opt-out's verdict and ends only its own flight

With hooks read off, an approval's settle always concluded 'removed', which the
routing check treats as usable. If an opt-out during that approval could not
read hooks.json, it had concluded 'unavailable' because the entry may still be
there, and the settle overwrote that. The settle now keeps 'unavailable' when
hooks are off; the next hooks-off check or opt-out re-derives it as before.

The settle's fallback when it cannot run now clears the running approval only
if it is still its own, and the routing check's comment states its rule: never
usable while an approval runs.

* fix(codex): spell the system cmd.exe with backslashes

Under Codex's cmd.exe host the outer cmd.exe hands the typed program text to
the child verbatim, and cmd.exe scans its whole command line for switches, so a
forward-slash C:/Windows/System32/cmd.exe is read as switches: the hook never
runs ("The syntax of the command is incorrect.") and /d is lost. Measured live
on Windows; both PowerShell hosts rewrite argv0 and were unaffected. The script
path after @" keeps forward slashes.

* chore(codex): say why the cmd.exe path is absolute, as measured on Windows

* test(codex): Windows managed-install tests expect the frozen command

They still asserted main's PowerShell text and a backslash bare path; they only
run on Windows, so nothing here caught it. Also correct the /v:off comment: a
lone ! is never dropped, only a !NAME! pair expands.

* ci: run the Codex managed-install tests in the Windows job

Its Windows-only cases skip everywhere else, so nothing ran them; three of them
still asserted a command this branch no longer writes.

* ci: a change to the Codex managed-install tests starts the Windows job

Also say what the missing-script case asserts: a non-zero exit, which
PowerShell reports as 1.

* chore(codex): name the hook trust key pattern for what it matches

* test(codex): the managed-install tests remove folders with the retrying helper

Now that they run in the Windows lane, a raw recursive rm there can throw EPERM
after the assertions pass.

* refactor(codex): one Codex hook-trust key pattern for the trust move and #23958's carry

* test(codex): the trust move carries a block in Codex's quoted spelling and leaves no second table
2026-09-30 14:26:23 -07:00
28d617d0d0 fix: close abandoned PDF.js development asset streams (#23054)
* fix: close abandoned PDF.js development asset streams

* test: keep casting safety comments attached after formatting

* fix(i18n): restore diff note draft catalog entries

* fix: guard closed PDF streams and preserve foreign listeners

* fix(i18n): make AI the recipient of diff notes

* test(browser): report the phase of WebRTC probe timeouts

* test: pin the historical hourly package version fixture

* test: provide the historical package version through build options

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 15:08:43 -07:00
OrcaWinandm4air 8416e8de10 refactor(persistence): retire ordinary JSON profile writes (#23202)
* refactor(persistence): retire ordinary JSON profile writes

Require SQLite for writable profiles and keep import, compatibility export, and recovery in a documented legacy-json boundary.

* fix(cli): preserve dynamic profile imports in release output

* test(persistence): exercise SQL races and verify packaged CLI imports

* test(persistence): consolidate shared fixture imports

* test(persistence): close SQLite fixtures before cleanup and await launcher output

* test(automations): use SQLite fixtures for dispatch fencing and skip coalescing

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 12:56:32 -07:00
OrcaWin 82412dab8b Persist profile state in SQLite with background writes (#22612)
Migrate profile state to SQLite and move writes and backups into a background worker. Acknowledge terminal, SSH and automation changes only after durable saves. Preserve JSON import, recovery, rollback and compatibility exports.

Validate migration, worker failures, maintenance, cross-profile moves and terminal lifetime races with unit, integration and end-to-end coverage.
2026-09-25 22:47:33 -07:00
OrcaWin 54a19c2ba3 fix(pdf): render CJK text with pdf.js resources
Merged after PR-specific checks passed. CI failures are unrelated baseline findings in ClientHostedBrowserPagePane.markup.test.tsx and pane-title-update-global-scan-budget.test.tsx.
2026-09-19 01:28:09 -07:00
Jinwoo Hong 631b51f508 perf(usage): run the Claude/Codex/OpenCode usage scans on a worker thread (#21114)
* perf(codex-usage): resume rollout scans at the last parsed byte

Codex rollout files are append-only and grow all day, but any append
changed both mtime and size, so `canReuse` discarded the cached entry and
the scanner re-read the whole file from byte 0 on the Electron main
process. On one real corpus that was 6.59 GB re-read per cycle across
26.63 GB / 21,110 files.

Each parsed file now persists a resume point: the offset just past the
last newline-terminated line, the parse context at that offset (session
id, cwd, model, running totals), a sha256 of the 4 KiB before it, and the
file's dev:ino. A grown file resumes there and merges the appended
rollup into the cached one; anything unproven falls back to a full
reparse — truncation, an in-place rewrite, rotation, a counted tail with
no trailing newline, a legacy copied-session suffix offset, or a file
that must reclaim deferred fork claims. Resume never depends on mtime
equality, so a coarse-mtime filesystem cannot hide an append.

Fixture: a 75,737-byte rollout with a 758-byte append re-read 76,495
bytes before and 8,950 after (the append plus two bounded 4 KiB boundary
windows).

Also bounds the automation-attribution force predicate for both Codex and
Claude: it keyed on `lastScanError`, so a persistently failing scan forced
a fresh full rescan on every single lookup. It now keys on the most recent
scan attempt, which is one forced scan per run regardless of outcome.

* perf(usage): run the Claude/Codex/OpenCode usage scans on a worker thread

The three first-party usage scans walk whole rollout and transcript corpora
and read OpenCode's SQLite synchronously, all on the Electron main process.
They rarely produce a long stall — the JSONL reader streams, so it yields to
the loop between chunks — but they pin the main-process event loop at ~95%
utilization for the scan's whole duration, which is what every IPC message,
timer and window event then queues behind.

Move that work to one lazily-spawned, unref'd worker thread shared by all
three providers, following the OpenCode SQLite scanner precedent (#8864).
Measured on a synthetic 4,000-rollout corpus (25.8 MB cache): a cold scan
drops from 2,147 ms of main-thread time to 31 ms, and a steady-state
incremental scan from 165 ms to 64 ms.

The worker is stateless and the cache crosses the boundary both ways. That
costs ~64 ms of structured clone at this corpus size, against 2,147 ms saved
on the cold path, and it keeps the persisted cache the single source of
truth — a worker-owned copy would need an invalidation protocol and a second
resident copy of the same multi-MB array.

Failure is closed, never a silent empty result: a worker that cannot spawn,
times out, or crash-loops rejects, and the store records the scan error and
keeps the previous projection.

Two clients already carried the same FIFO/timeout/crash-cap machinery, so
extract it once as WorkerThreadRequestQueue (with the packaged entry-path
resolver as worker-thread-entry-path) and move all three onto it, rather
than adding a third copy. Their existing tests pass unchanged.

The oracle is event-loop utilization on the calling thread, not a stopwatch:
usage-scan-worker-event-loop.test.ts runs the same scan both ways and asserts
the worker leg leaves the caller idle while the main-thread leg does not, so
CI load moves both legs together (#18788).

* test(usage): compare the two scan arms instead of two fixed thresholds

The event-loop oracle claimed to be self-calibrating — its header said "the
ratio is self-calibrating, so CI load moves both legs together (#18788)
instead of tipping a fixed millisecond threshold." It computed no ratio. Two
separate `it()` blocks each asserted an absolute threshold against its own
arm, run separately, so load moved them independently. The comment described
a test nobody wrote, and the flake it promised was impossible is the one that
landed: `activeRatio > 0.8` on the calling-thread arm measured 0.764 on an
ubuntu runner.

Fixing the comment is not enough, because the fraction is the wrong quantity.
CPU contention drags the calling-thread arm's active/wall fraction *down*
toward the worker's, since the loop parks waiting on a contended libuv pool.
A 4-vCPU Linux container measured that arm at 0.175-0.756 across twenty runs,
idle and loaded — never once above 0.8. Active *milliseconds* move the other
way: contention stretches the caller's JS time far more than it stretches the
worker arm's fixed post-and-deserialize cost, so the gap widens under load.

Merge the two arms into one case over one corpus and assert the worker arm
costs the caller under a fifth of the inline arm's active milliseconds. Same
twenty Linux runs: 10.9x-83.6x, passing throughout. Keep the presence
preconditions on both arms — an arm that silently scanned nothing satisfies
the comparison trivially — and extend them to the calling-thread arm, which
previously checked only file and session counts.

* fix(ports): name the dropped command when the probe queue is full

The shared-queue extraction turned `Port scan command queue is full; dropped
${command}.` into a constant string, because `describeFull` was given no way
to see the request. Pile-up is per-probe, so the name is the only thing in
that log that identifies which of lsof/ps/netstat was shed.

Pass the rejected request to `describeFull` and restore the name. The request
is built before the cap check so it exists to be named; the id it burns is a
correlation token, so a gap costs nothing.

The existing overflow test asserted only the error class, which is why the
regression escaped a 29-test suite. It now dispatches the overflow under a
different command than the accepted ones and asserts the message text, so a
message that names the wrong request fails too.

Also add a direct WorkerThreadRequestQueue test. Three subsystems share the
queue and each client test only sees the parts its own protocol exercises,
with `queueCap` reachable from port-scan alone. Covers one-at-a-time FIFO
dispatch, the deadline starting at dispatch rather than enqueue, the
consecutive-death cap, and both points where that count clears.

And record the child-process hazard at the usage worker entry. `terminate()`
reaps nothing the thread spawned, and OpenCode discovery reaches a fork
today: `wslGated*` forks the WSL transcript sidecar for a `\\wsl$\...` path,
which a Windows `OPENCODE_DB` or `XDG_DATA_HOME` can be. One scan through
that entry with a UNC `OPENCODE_DB` forked a sidecar that outlived
`terminate()`.

* test(ai-vault): assert the OpenCode worker messages exactly, not by fragment

Checked every message string in the two clients the shared-queue extraction
rewrote against origin/main. Only the port-scan queue-full one regressed
(fixed in the previous commit); the OpenCode SQLite client's four messages
render identically, the remaining source diffs being renames — `error.message`
to `lastError`, `call.timeoutMs` and `CALL_DEADLINE_MS` to `timeoutMs`.
`session-scanner-worker-client.ts` was not touched by the extraction.

But its suite could not have caught it either. `/timed out/`, `/exited with
code/` and a bare `rejects.toThrow()` all still match a message that has lost
its interpolated value, which is the same blind spot that let the port-scan
regression through. Assert the rendered text instead: the timeout names its
deadline, the exit names its code, and the crash-loop drain still carries the
text of the fault that killed the run.

* fix(usage): correct the worker entry's child-process note

The previous note said `worker.terminate()` leaves a forked sidecar orphaned.
It does not, and the reproduction that appeared to show it used a stub sidecar
missing the `process.on('disconnect', () => process.exit(0))` the real entry
has. With a faithful one: the sidecar lives exactly as long as the thread and
is gone within 2s of `terminate()`, because tearing the thread down closes the
IPC channel it owned. Two worker lifecycles forked two sidecars and leaked
neither, and the pre-worker main-thread path reaps its sidecar the same way,
on host exit.

What is true and worth recording: a fork is reachable from this bundle at all,
which is easy to miss; it survives only as long as the channel does; and the
sidecar is now re-forked per worker lifecycle instead of pooled for the app's
life. State those, and warn that a future child which does not exit on channel
close would not get the same free cleanup.

* fix(usage): kill a wedged scan worker on no progress, not on wall clock

`USAGE_SCAN_TIMEOUT_MS` was a 10-minute deadline on the whole scan. A cold
scan of a real history is legitimately minutes — 637 s measured on a 30 GB
corpus with 300 worktrees before the per-cwd memo, ~51 s after — so a
larger corpus or a slower disk crosses it. Crossing it killed the worker,
recorded a scan error and left the cache unadvanced, so the next refresh
started cold and died at the same point, forever.

The deadline is now a no-progress window. The worker posts a file counter
as it walks the corpus (`UsageScanWorkerProgress`, rate-limited to one
message a second), and `WorkerThreadRequestQueue` re-arms the active
call's timer on each one via the new optional `isProgress`. Clients that
do not pass it keep the plain wall-clock deadline. `MAX_CONSECUTIVE_DEATHS`
and idle teardown are unchanged.

* refactor(usage): report scan progress as a file count, not one call per file

Claude's scanner walks batches, so a per-file callback made it loop just
to bump a counter.
2026-09-16 23:03:37 -04:00
Neil 26721bd632 fix(codex): stop blocking the main thread on trust grants (#16441) (#16594)
* fix(codex): stop blocking the main thread on trust grants (#16441)

Codex hook trust was granted by blocking the Electron main thread on
`spawnSync` of a bundled ELECTRON_RUN_AS_NODE entry for the whole
app-server deadline: 15s native, 35s WSL, ~45s on the real-home path
(rebase inspect + repair + grant). Cold start and every Codex pane
launch showed "Not Responding"; the reported event-loop gap was
15,049 ms.

The subprocess only ever existed to donate an event loop to a
deliberately blocked parent — `runCodexHookTrustGrantSession` was
already the real async implementation. Make the callers async and the
fork is unnecessary, so the bridge, the forked entry and its envelope
are deleted along with their build/knip/tsconfig registrations. The CLI
`agent hooks prepare-codex` handler is already async, so it awaits the
in-process session and saves a process spawn per managed-home shell.

`resolveCodexTrustGrantHost` is async too; the WSL identity probe moves
from `execFileSync` to `runProcess`, dropping that file from the
child-process import allowlist. Status reads keep a synchronous
native-only stamp path.

Two invariants that held only because the lane blocked:

- Overlapping capability probes were impossible by construction.
  `GitCapabilityCache`'s dedupe engine is extracted to a shared
  `CapabilityProbeCache` and `CodexAppServerCapabilityCache` now
  inherits it, so concurrent launches against a cold host share one
  app-server session instead of one each.
- Two grants on one `config.toml` could not interleave capture and
  restore. A reentrant per-file lane now serializes the whole install
  sequence (managed, WSL runtime, real-home ensure, legacy sweep) and
  the grant and rebase inside it.

Cold-start work moves off the critical path: retained-home
reconciliation (N sequential sessions) is fire-and-forget behind the
daemon provider, and the startup real-home ensure chains into managed
hook reconciliation instead of blocking app init.

Every preserved semantic is unchanged: never throws, the
ORCA_DISABLE_CODEX_TRUST_RPC kill switch, ledger hits, backfill-pending
and cooldown fallbacks, config rollback on every failure path,
pre-grant self-computed trust removal, the verify-failure taxonomy,
diagnostics and telemetry.

* fix(codex): widen the trust-config lane to every config.toml writer

Review follow-ups on #16441's async trust grant:

- `markCodexProjectTrusted` now runs inside the runtime+system config.toml
  lanes, so a project-trust write can no longer land inside a hook grant's
  capture->restore window and be silently reverted. Its callers await it.
- `install`/`refreshRuntimeUserHooks`/`remove` hold the system config.toml
  lane as well as the runtime one — they promote approvals into
  ~/.codex/config.toml and mirror it back. Lock order is runtime-before-system
  everywhere.
- The real-home ensure chain resumes after a rejection instead of returning
  the same rejected promise to every later pane launch, and resolving the real
  home is now inside the module's never-throws boundary.
- `buildSpawnEnv` awaits inside a cancelable pending-spawn registration, so
  shutdown during the (now long) env build stops the PTY from launching.
  `prepareLocalPtySpawn` generalizes into `awaitCancelableLocalPtySpawn`.
- CapabilityProbeCache drops the test-only `nowMs` passthrough; its probe
  backstop comment now describes what it actually guards.
- Preflight is a plain async function; the trust dispatch in orca-runtime
  collapses into one `markWorkspaceTrustedForAgent`.

* test(codex): exercise the trust-config lane under real concurrency

The async grant makes two pane launches overlap for the first time. These
drive the real modules end to end on real files: a rollback swallowing a
sibling's grant, a markCodexProjectTrusted write landing inside a capture
-> restore window, shared capability-probe dedupe on a cold host, the
host-scoped transient cooldown, and reentrancy from inside an installer.

Each was verified to fail against a deliberately broken implementation
(lane removed, dedupe disabled, cooldown made global, reentrancy pass-
through disabled).

* test(codex): stop hook-service suites spawning the developer's real codex

The forked grant bundle never existed under vitest, so the RPC lane was
unreachable in tests on main. Running it in-process makes these suites
spawn a real `codex app-server` when one is installed: 38 spawns and two
failures in hook-service-runtime-trust-repair on a machine with codex,
green in CI where there is none. Stand in for the missing binary so both
environments exercise the same fallback lane.

* docs(codex): scope the trust-RPC kill switch comment to what it actually gates

The comment read as though the flag forces the fallback lane everywhere. It
gates the managed grant only: the real-home rebase still runs its own
inspect/repair app-server sessions when Orca's insertion shifts a user's hook
positions, and never reads the flag.

Verified by exercise, not by reading — with the flag set, both
inspect-user-hook-trust and repair-user-hook-trust still ran. Pre-existing:
main has no check there either, it just blocked the main thread while doing it.

Widening the flag to cover the rebase is a follow-up; this only stops the
comment promising something the constant does not do.
2026-08-26 16:44:55 -07:00
OrcaWinandOrcaWin 4ec6bbf588 Kill hung WSL transcript filesystem operations via child process with route quarantine (#15381)
* fix(native-chat): kill hung WSL operations via child process

Stalled UNC file operations hold libuv permits even after the gate
timeout expires, blocking Chat tab recovery. Two stalled operations
fill both permits and freeze all WSL access until restart.

Fork file I/O for UNC paths into a separate child process. On deadline
expiry, kill the process to force the hung syscall to exit. This frees
the permit for the affected tab's next read. Temporarily quarantine the
stalled route to avoid retry storms.

* chore: drop internal review artifact from the repo root

* fix(native-chat): harden the WSL transcript fs sidecar

Review follow-ups on the sidecar isolation change:

- Only the deadline may abort running gate work. The sole waiter's
  same-duration timeout fired first, killed healthy children on caller
  abandonment, and settled the task before the deadline could quarantine
  a stalled route - leaving the back-off dead for every dedupe:false op.
- Resolve the fork entry from out/main/chunks too: the resolver compiles
  into a shared chunk, and the scanner service child has no
  process.resourcesPath, so packaged WSL vault scans threw entry-not-found
  (masked as an empty tree).
- Allowlist the fork env instead of spreading process.env; ambient
  NODE_OPTIONS would halt or --require code into every child.
- Wrap transport faults (spawn failure, child death) in
  WslTranscriptFsError('unavailable') so discovery reports them as scan
  issues instead of misreading them as missing paths or empty trees.
- Gate the vitest in-process fallback on the vitest worker global so a
  leaked VITEST=true cannot revert production to in-process UNC syscalls.
- Reap idle sidecar processes after 60s instead of holding them for the
  app session.
- Split 'open' into its own protocol union member so the reusable-call
  Exclude actually strips it from the pooled-process API.
- Guard kill('SIGKILL') against the teardown race where an exiting child
  emits an unlistened 'error', and dispatch reads by handle kind before
  path spelling.

* fix(native-chat): probe stalled WSL routes instead of a fixed quarantine

Remaining review follow-ups:

- Escalating route quarantine: first strike lifts after 5s so a distro
  that was cold-booting when its op hit the deadline recovers on the
  next poll (~35s total instead of ~90s); repeat stalls double the
  back-off toward the prior 2x-timeout cap, and any settle the deadline
  did not force clears the strikes. Queued same-route tasks fail fast
  at quarantine instead of stranding one waiter deadline per file in
  sequential scans.
- Single request implementation: the vitest in-process fallback now runs
  the child's own dispatcher (WslTranscriptFsProcessOperations + decode),
  so unit suites exercise exactly what the forked process executes and
  the per-call-site fallback closures are gone. Dirent fixtures gained
  the full kind-flag set the serializer reads.
- Dropped the production-dead per-route close queue; UNC FileHandles
  (test fallback only) mirror the process-handle close contract.
- Error class, messages, and factories move to wsl-transcript-fs-error
  (re-exported from the gate) to keep the gate under the lines budget.

* fix(native-chat): harden WSL transcript fs with route quarantine strike

Extract quarantine logic into a dedicated module with strike decay: stalls older
than 5 minutes restart from base back-off, and concurrent-lane timeouts count as
one incident. Allow joining live in-flight tasks on quarantined routes (they cost
no new I/O). Preserve quarantine across transport faults (child death). Handle
file shrinking during tail reads by detecting short reads and returning empty.
Defer file closes that arrive mid-read instead of refusing, preventing slot
leaks. Separate process slot and boundary-finding concerns into focused modules.

* fix(native-chat): enforce route quarantine windows and isolate lanes per

A late result arriving after the deadline was incorrectly lifting the route
quarantine, allowing subsequent work to start before the back-off period
expired. Now late results are correctly recognized as stale and never cut
the quarantine short.

Process work is now isolated per (route, priority) lane so a scan stall
cannot block exact reads on the same distro. Each lane gets its own client
and process pool; late results and handle faults stay scoped to their lane.

Tests now fake performance.now() alongside timers (the quarantine clock
depends on it) and wait for the full back-off window to expire rather than
advancing by 0. Gate state is reset between test cases since late releases
never lift the quarantine.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-18 16:57:35 -07:00
Jinjing 4c69e45552 Strengthen plain-node-entry-guard with entry name validation (#12761)
* Strengthen plain-node-entry-guard with entry name validation

- Add buildStart hook to validate guarded entry names exist in rollup
  inputs, preventing stale names from silently stopping guards
- Extend electron require detection to subpaths (electron/main, etc)
- Improve smoke test signal/exit handling and use constants
- Add comprehensive tests for entry validation and new behaviors

* Add SIGKILL escalation to plain-node-entry-guard timeout

Switch from spawnSync to async spawn to properly handle daemons that trap
SIGTERM. spawnSync's timeout only sends the signal and waits, so a daemon
that ignores SIGTERM causes the build to hang. The new runDaemonEntry
function escalates to SIGKILL after a grace period to enforce the deadline.
Configurable timeouts and grace periods via SmokeTimings type; closeBundle
hook becomes async to support the change.
2026-08-11 12:20:20 -07:00
Brennan Benson 5df2ddbc9c perf(ai-vault): isolate tab title resolution (#13377)
* perf(ai-vault): isolate tab title resolution

* fix(ai-vault): preserve background scan caches

* fix(ai-vault): resolve nested worker from chunks
2026-08-09 16:45:52 -07:00
Jinjing fde816e4ee move folders (#12758) 2026-08-05 12:09:24 -07:00