* rm git shim
Drops the terminal git/gh PATH wrapper and its settings toggle. Renames the no-marker shell-ready launch config after what it does.
Co-authored-by: Orca <help@stably.ai>
* rm git shim: clear stale state from older installs
Deletes the orphaned wrapper dir and scrubs inherited env/PATH, so a daemon that outlives the upgrade cannot keep seeding it. Drops a now-unread spawn option.
Co-authored-by: Orca <help@stably.ai>
* rm git shim: cover the daemon and headless paths
Scrub after the PATH prepends (they re-read process.env on the sparse daemon env) and run the cleanup above the serve branch so remote hosts get it too. Retry a locked removal; match PATH case-insensitively.
Co-authored-by: Orca <help@stably.ai>
* rm git shim: keep the scrub final
Refuse to re-prepend a legacy entry during agent-teams PATH promotion, which runs after the scrub. Cover the removal guard.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(worktrees): make hidden agent worktrees recoverable from the visibility dialog
A discovered agent scratch worktree (.claude/worktrees, .gsd-workspaces)
was a one-way door: the non-Orca visibility toggle never reveals scratch
by design (#9388), the inbox never announces it, and the dialog listed
nothing — so once hidden it was unreachable from the UI while sitting on
disk. The per-path import exception has outranked the hidden rule all
along; no surface offered it.
The dialog now refetches an authoritative list on open (a stale snapshot
must not read as 'nothing hidden'), lists hidden importable worktrees,
and offers a per-row Show wired to the existing inbox import action,
which already merges the import + baseline and rolls back on a failed
refresh. When the list cannot be read the dialog says so and offers a
retry instead of claiming the repo has nothing.
No new settings, schema, or persistence: recovery rides entirely on
importedExternalWorktreePaths, which every host already stores and
validates. The repo-wide toggle is untouched and still never reveals
scratch.
Fix#10324
Co-authored-by: Gldywn <14254051+Gldywn@users.noreply.github.com>
* fix(worktrees): honest scan states and race-safe row actions in the visibility dialog
- row Show stays disabled until the open-time authoritative scan settles;
a click mid-scan could join the pre-write refetch and read success off a
list computed before the import landed, a silent no-op on slow hosts
- checking/failed indicators follow the scan state alone, so a warm older
snapshot cannot present stale rows as current with no failure indication
- ownership-neutral section copy: non-scratch rows are listed too when the
repo-wide switch is off
* fix(worktrees): close the retry race window and clear stale failure state
- Try again is locked while a row import is in flight; a retry scan
started before the import's write lands can absorb the import's own
refetch and report success off a pre-import list
- a successful row import (which requires a successful authoritative
refetch) clears an earlier failed open-time scan instead of leaving a
contradictory alert over the refreshed list
- zh: 智能体 for agent (代理 reads as network proxy); polite live region
on the checking hint
* fix(worktrees): serialize visibility dialog actions
* fix(worktrees): clarify persistent visibility policy
* fix(worktrees): clarify hidden worktree list
* Explain hidden worktree defaults
* Show agent worktrees with Always show
* fix(worktrees): bound visibility dialog state and rendering
* fix(worktrees): preserve visibility mutation fences across dismissal
* fix(worktrees): scope visibility mutations by host
---------
Co-authored-by: Gldywn <14254051+Gldywn@users.noreply.github.com>
* fix(terminal): carry kitty flags through Preview snapshots and pair rele
Preview was omitting the live kitty mirror from the IME bridge and dropping kitty flags from snapshots, so every commit was evaluated at flags 0. A TUI that negotiated bit-3 (report_all_keys_as_escape_codes) would receive the legacy raw text it declined.
Now the snapshot carries proven kitty flags beside their sequence boundary, the forwarder reads flags once per commit, and bit-1 (report_event_types) commits are paired with exactly one release regardless of keyup/insertText ordering. Snapshot authorities expose only the active screen's proven flags, so an old host's absent field stays unknown rather than downgraded to a manufactured zero.
* fix(terminal): sync kitty flags and IME releases across snapshots
* trim wordinesss
* fix(terminal): settle owed IME release before fresh same-key press
When a keyup is lost and the same key is pressed again, settle the stale
record's owed release instead of discarding it — this maintains correct
IME state during recovery. Also refine Kitty flag propagation to only
carry proven baselines across snapshots, and tighten related comments.
* fix(terminal): gate kitty flags on sequence boundaries
- Remote snapshots only include flags when seq is present
- Daemon uses parsed flags value when defined
- Ensures correct flag ordering in snapshot replay
fish arms `CSI ?2031h` before painting each prompt and withdraws it when it
hands the tty to a child — a ~1ms window. Orca answered that subscribe with
`CSI ?997;Nn` across a 1-3ms renderer hop, so the reply landed after the
withdrawal and was read as stdin by the next child, corrupting `brew`/`npx`
`[y/N]` prompts.
The reply is not stale by Orca's own view when written (measured
staleReplies: 0), so no suppress-the-stale-reply scheme can close this — the
information needed to suppress does not exist yet. Nothing asked for the reply
either. The Contour spec says a terminal "should only send out the DSR when the
palette has been updated"; Ghostty (Termio.zig:729 — force=true reachable only
from the ?996n DSR), iTerm2 (VT100Terminal.m:995 — flag only) and xterm.js
(InputHandler.ts:2035 — flag only) all emit nothing on the DECSET. So stop
entering the race: record the subscription, answer nothing.
Of 17 real programs measured under a pty, only fish, tmux, claude and opencode
subscribe; none block on a reply, and answering produces one redundant palette
re-query and zero rendering difference. tmux is the only one that sends `?996n`,
which Orca still answers.
- Subscribes are record-only at all four emitters (live scan, hidden-gate fact,
parked byte watcher, parked responder — the last is deleted, it only replied).
- `?996n` answers, the subscription registry, and the theme-flip push are
unchanged. `paneLastThemeMode` is still seeded at subscribe so the next
appearance re-apply is not read as a flip.
- Replay grammar carries `?2031l` alongside `?2031h`, so a late-attaching remote
client no longer registers a subscription the TUI already retired.
Also closes fish-integration gaps found alongside: `unset` (which fish lacks)
becomes `set -e` on paths parsed by the client's login shell, `config.fish` is
parsed for agent-home detection, and bracketed-paste startup delivery is made
consistent across local/daemon/relay.
Regression test drives real fish 4.7.1 under node-pty and asserts on what the
child process reads; it fails against pre-fix code with the exact payload from
the issue. CI installs fish 4 and fails loudly rather than skipping.
Closes#9993
Co-authored-by: Orca <help@stably.ai>
Enable eleven oxlint rules that simplify code without changing behavior, and fix
every existing violation. Each candidate was gated on measured cost rather than
assumption, so rules that regressed runtime performance or type checking were
dropped instead of suppressed.
typescript/no-redundant-type-constituents is the largest addition: 113 sites, no
autofix. Dead constituents are deleted. Where the redundant literal existed to
document intent (`string | 'all'`), it is preserved as `(string & {})`, which
keeps the autocomplete hint the original code was reaching for instead of
flattening it away. The rule also caught a broken import —
remote-shared-control-retirement-probe.ts pulled RuntimeStatus from
src/shared/types, which does not export it, so the type silently degraded to
`any`; no tsconfig covers that file, so tsc never saw it.
oxlint stays at 1.77.0 rather than 1.78.0 because .npmrc sets
minimum-release-age=4320 and 1.78.0 is younger than that window.
Rules evaluated and rejected, with what disqualified each:
- prefer-string-raw: String.raw is a runtime call, not a literal (184x slower)
- prefer-string-replace-all: 26% slower
- text-encoding-identifier-case: ~5% slower, reproducible
- prefer-spread: [...str] is 110% slower than split('') and differs on surrogates
- no-implicit-coercion: `!!x` narrows types and `Boolean(x)` does not (22 tsc errors)
- prefer-arrow-callback: arrows are not constructible, breaking `new` on mocks
- object-shorthand: rewrites source text asserted by a tracked reliability gate
- switch-case-braces: pushes ten files past max-lines, which cannot be suppressed
- no-useless-switch-case: drops `case undefined:` that switch-exhaustiveness-check needs
- arrow-body-style: 115 violations have no fix, and it breaks max-lines
- newline-after-import: false-positives on the leading-semicolon ASI idiom
electron-vite-output-contract asserted on the literal
Object.prototype.hasOwnProperty.call text; retarget it to Object.hasOwn, which
rejects inherited keys identically.
* fix(windows): refresh persisted PATH ordering
New terminals now preserve Orca-injected entries while adopting current machine and user PATH precedence, so newly installed tools are not shadowed by stale aliases. Invalidate the cached registry snapshot on Windows setting changes and guard in-flight refreshes from restoring stale cache data.
* fix(windows): retry invalidated PATH reads
* fix(windows): reuse current PATH refresh
* fix(windows): preserve per-hive PATH fallback state
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Adds GitHub stacked pull request creation: a contextual "Stack this PR above #N" option that appears only when the selected base branch has an open PR, plus the main-process stack preflight and registration.
Also reworks the create-review composer for cohesion: shadcn Checkbox and Label primitives, base label above a full-width searchable combobox with attached results, keyboard navigation, and a unified field skin, spacing and typography scale.
Verified end to end against real GitHub: extending an existing stack and creating a new one.
* feat(bitbucket): connect Bitbucket from Settings with encrypted credential storage
Bitbucket Cloud was the only review provider with no in-app auth: GitHub and
GitLab delegate to the gh/glab CLIs, but Bitbucket has no comparable
first-party CLI, so the only option was ORCA_BITBUCKET_* env vars plus a
restart (discussion #5364).
Adds a Connect/Edit/Disconnect flow on the Bitbucket integration card,
modeled on Linear and Jira:
- Credentials are verified against /user before they are persisted, so a
dead token is rejected inline instead of silently stored.
- The secret is encrypted with safeStorage (0600 plaintext fallback when no
OS keyring); non-secret metadata lives in a separate plaintext file so
status reads render the connected account without decrypting. Opening
Settings therefore never triggers a keychain prompt.
- Env vars keep precedence over stored credentials, so existing headless and
SSH setups are unaffected. Env-managed connections hide Disconnect.
- connect/disconnect reset the preflight cache, so no relaunch is needed.
The Bitbucket card moves to its own file to stay under the tsx max-lines cap.
* feat(bitbucket): support creating pull requests from Orca
Bitbucket was the only configured provider whose Create button reported
"This repository provider does not support creating a pull request from
Orca" — supportsReviewCreation was false and the forge provider had no
createReview, so even a correctly authenticated setup was blocked.
Adds createBitbucketPullRequest against POST /repositories/{ws}/{repo}/
pullrequests, using the same env-first / stored-credential resolution as PR
lookups (extracted into resolve-auth.ts so both share one path).
Bitbucket Cloud has no draft pull requests, so a draft request is rejected
with a clear message rather than silently publishing a live PR.
* fix(bitbucket): hide the draft toggle where drafts do not exist, plus review fixes
Bitbucket Cloud has no draft pull requests, so the composer no longer offers
the toggle for it and forces the flag off at submit — better than failing
after the user has filled the form in.
Review fixes:
- writeFileSync's `mode` only applies when it creates the file, so rewriting
a credential kept whatever permissions it already had. chmod after every
write, for the secret and the metadata.
- An explicit ORCA_BITBUCKET_API_BASE_URL now wins over a stored base URL.
Env precedence is per-setting, not all-or-nothing.
- Enter in the credentials dialog only submits from a text field, so it no
longer hijacks Cancel and the docs link.
- Replace the chmod-based delete-failure test with a mocked unlinkSync: file
modes are not portable to Windows and elevated runners unlink anyway.
* fix(bitbucket): stop a merged pull request from blocking the branch's next one
Reported on #5832: with a merged PR on a branch, Create reported "Pull
request already exists" and offered no way forward.
The branch lookup queries every PR state and returns the most recently
updated one, so a merged PR came back as the branch's current review and
eligibility blocked on it. Bitbucket only discarded such a match on the repo
default branch (#9171), while GitHub already drops any merged PR it matched
by branch alone — "a merged PR without an explicit link is just a historical
branch match, not implicit review context".
Applies that rule to Bitbucket. An explicitly linked review still resolves
through the linked-number fallback, so merging a PR Orca knows about keeps
showing it.
* fix(bitbucket): add bitbucket to the shared review-creation provider list
Reported on #5832: on a Bitbucket repo with no existing PR, Create still
said "This repository provider does not support creating a pull request
from Orca", even after the forge provider gained createReview.
There are two capability lists. Enabling supportsReviewCreation on the forge
provider was necessary but not sufficient — the blocker and the whole
renderer read the separate shared list, which never included bitbucket.
Adds it, gives Bitbucket its own provider name so review copy stops saying
"GitHub", and asserts the two lists agree so they cannot drift apart again.
* fix(bitbucket): persist pull request links after creation
* fix(bitbucket): fetch linked pull requests by number first
* fix(i18n): use generated Bitbucket integration keys
* test(bitbucket): cover forge creation delegation
* fix(bitbucket): fall back when linked pull request is stale
* docs(bitbucket): explain notFoundIsNull and fix a garbled permissions comment
notFoundIsNull arrived without the rationale its sibling flag carries, and
reads as a bare `true` at the only call site that opts in.
* fix(bitbucket): address review findings before merge
Two of these made the feature unusable in real setups:
- Create PR checked GitHub authentication for Bitbucket. isProviderAuthenticated
fell through to isGitHubAuthenticated, which was unreachable while Bitbucket
could not create reviews at all. Anyone with Bitbucket connected but no
`gh auth login` got auth_required with no way forward.
- The draft flag was only gated in ChecksPanel, not the two SourceControl call
sites. With "create as draft" saved as a default, the composer hides the
toggle for Bitbucket, so the flag could not be cleared and creation failed
every time. Bitbucket now ignores draft instead of rejecting it.
Also:
- Blocked-create copy said "GitHub is not authenticated. Run gh auth login" on
Bitbucket repos, in both the main-process and renderer paths.
- A decryption failure resolved to an anonymous config and queried anyway; a
private repo answers 404, which reads as "no pull request" and offers Create
for a branch that already has one. Requests now fail closed.
- Hiding non-open implicit branch matches was too broad: a declined PR became
permanently invisible off the default branch. Scoped to merged, restoring the
default-branch rule (#9171) for the rest.
- A failed disconnect rejected unhandled and the card silently re-rendered as
connected; a partial delete left the secret live in memory for the session.
- The credentials dialog refused to open on a remote runtime, so a local repo
could never store a credential. Now only the storage note changes, matching
the Jira dialog.
---------
Co-authored-by: devatnull <59279509+devatnull@users.noreply.github.com>
* fix(terminal): explain a dead terminal host instead of leaking a socket error
When the daemon that owned a session has exited, resuming that terminal surfaced
the raw connect error for its endpoint — on Windows a named pipe path, since a pipe
disappears with its server process — in a red toast ending in "file an issue". The
session is unrecoverable and the text is unactionable.
Translate it at the daemon boundary into terminal_host_gone, following the existing
terminal_pane_owner_unverified path, and humanize it in the toast. Suppress the
issue link for a condition Orca can fully explain.
Covers the remote-host case: the conversion sits in attachStablePaneOwner, the first
catch a paired client's resume reaches before the error is serialized onto the wire.
* fix(terminal): preserve host-gone errors across versions
* fix(terminal): scope host-gone error matching
* refactor(terminal): derive the host-gone test and replace forms from one source
Two regex literals matched the same token with subtly different shapes
(capturing vs non-capturing, consuming vs lookahead), so an edit to one could
silently drift from the other. Build both from a single source string; the test
form stays non-global so it carries no lastIndex state between calls.
* test(terminal): cover host-gone regex boundaries
* test(terminal): isolate leading host-gone boundaries
Forward the renderer's already-pinned {mergeBase, headOid} to the SSH relay so a single-file branch diff reads the two blobs directly instead of rediscovering live HEAD. Six sequential git processes become two concurrent reads, and a branch move mid-review no longer changes which revision is displayed.
Equivalence with the legacy route is proven against real Git across 14 change types; wire compatibility is proven over a real SSH socket against relay bundles built from main and from the pre-merge-base.
A write landing during a full scan was recorded by the post-scan gate stat,
so the next tick saw a clean gate and the create/delete waited up to 15 ticks
(~30s) for the backstop. Sample each gate dir's signature before its listing
so a racing write reads as stale and forces one rescan on the next tick.
Also fixes the flaky 'retires stale marker probes' spec: a folder caught
mid-rm opened a pending-marker window that the swallowed gate change kept
alive for the whole backstop gap (43 probes vs the 30 budget).
Co-authored-by: Orca <help@stably.ai>
* perf(codex): skip migration scans for nonshared reattaches
* fix(codex): require provenance to skip migration scans and bound ignored launches
Drops the exit-match escape hatch that skipped a scan without route provenance, and ages out ignored reattach launches on the existing 60s/256 lease policy so a lost exit can no longer pin an active launch (and its completion marker) for the process lifetime.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): skip a too-wide alt frame on snapshot replay
Reopening a parked worktree could paint a stale full-width TUI frame
through a narrower viewport, leaving clipped gutter fragments and
mid-word omissions until the live application repainted.
Replay pins xterm to the snapshot grid so soft-wrapped normal-buffer
history stays exact (#7279), then the post-replay fit returns the pane
to its container grid. Alternate buffers have no scrollback and do not
reflow; an absolutely positioned frame remains at its capture layout,
so narrowing exposes only clipped portions of those fixed-grid rows.
Skip only the visual frame when its capture is wider than the grid the
fit will land on. The alt buffer is still entered and cleared, so the
resize signal lands on a clean screen that the live application can
repaint. Equal-width and wider restores retain the frame, while normal
history always replays at its capture grid before fitting.
The target width comes from proposeDimensions, not terminal.cols: an
unfitted pane can still read xterm's default grid even when its actual
container matches the capture.
* fix(terminal): preserve offline SSH prepaint frame
* fix(terminal): drop a too-wide daemon alt frame on reattach
The renderer-only gate did not run on the user-visible remount path.
Instrumentation showed that daemon-connectResult-snapshot won before
the model-snapshot branches, so the composed snapshot painted in full
and the later narrower fit exposed its stale fixed-grid frame clipped
at the new viewport.
The daemon branch could not omit only the visual frame while main sent
one merged string. Publish the normal-buffer/mode prefix and visual alt
frame as additive optional metadata while retaining the merged snapshot
for mixed-version fallback. New renderers can keep history and restore
state without painting a frame captured for a wider grid.
Replay ordering remains capture-grid, write, then fit so normal-buffer
soft wrapping stays exact. The application owns the foreign-width alt
frame and repaints it after the resize signal rather than Orca trying to
transform an absolutely positioned screen.
* fix(terminal): preserve split daemon snapshot payload
* fix(terminal): preserve live state when dropping alt frame
* fix(terminal): restore DECOM cursor state exactly
* fix(terminal): preserve ordinary snapshot bytes
* test(terminal): cover fixed-grid alt replay resize
* fix(terminal): repaint after dropping mismatched frames
A hidden snapshot can omit an alternate-screen frame before the pane has a measurable target grid. If reveal later lands on the capture grid, a same-size PTY resize emits no SIGWINCH, so pulse the local PTY size whenever that frame was skipped.\n\nKeep performSafeFit's measurable-pane contract intact, publish daemon snapshot prefix and frame as explicit optional strings, and fall back to the merged payload when either field is absent. Cold owner-gone restores now omit a mismatched frame while retaining history and fresh-shell reset treatment; offline SSH preconnect remains unchanged.\n\nPin the vendored SerializeAddon out-of-range-row behavior used to capture live SGR state.
* feat(ai-vault): isolate scanning in service processes
* fix(ai-vault): retire idle service processes
* fix(ai-vault): discard unverified cache processes
* fix(ai-vault): clear relay sidecar cancel watchdog on acknowledgement
A cancelled relay call is settled before its 2s cancel watchdog is armed, so the acknowledgement path bailed out of settle() before clearing the timer. The watchdog then faulted a healthy sidecar two seconds after every aborted scan, killing whatever request had since become active.
* fix(ai-vault): clear the pending restart before scheduling another
recordFault overwrote this.timer, stranding a restart that dispose() could no longer cancel.
* refactor(ai-vault): drop the orphaned first-prompt IPC wrapper
session-first-user-prompt-handler.ts now owns this entry point and routes through the service; the copy left in the read module had no callers.
* fix(ai-vault): retry a faulted cold start before surfacing it
A slow first start surfaced a raw 'did not become ready' error to the caller even though the supervisor was already respawning. Requeue an unsent call once onto the scheduled respawn instead.
Also stop arming the cancellation watchdog for a call the child never received: no acknowledgement is coming, so it killed a healthy service and stalled the lane.
Invalidation bookkeeping and ready-waiter construction move to the state module to stay under the max-lines cap.
* fix(ai-vault): give relay title reads their own lane
Before this branch the relay read title files directly, concurrently with scans. Routing both through one sidecar lane put title resolution behind a list scan that may run up to 130s, so SSH tab titles could lag minutes behind.
Split cache and interactive lanes in both the relay client and the sidecar entry, mirroring the desktop service.
Also: clear the ready deadline on fault, so a sidecar that dies before ready cannot fault its healthy replacement five seconds later; retry an unsent call once across a respawn; and skip the cancellation watchdog for a call the sidecar never received.
Restart/circuit bookkeeping moves to its own module, mirroring the desktop policy, to stay under the max-lines cap.
* fix(ai-vault): degrade relay title resolution on sidecar failure
listSessions already returns a host issue when the sidecar is unavailable; titles propagated the raw RPC error instead. Return no titles so callers fall back to preview text, and keep cancellation propagating.
* fix(ai-vault): scrub the service child environment
The children are forked with a 384 MiB heap cap and no loader, but both
spawn sites handed them the full parent environment, so an exported
NODE_OPTIONS silently raised the cap or --require'd code into them.
Allowlist both, following the plugin worker. The desktop child keeps the
eleven agent-root overrides it resolves its own roots from; the relay
sidecar takes remoteHome and hostPlatform from its init message and so
needs none of them. Both children share one priority module while they
share this one.
* fix(ai-vault): soft-disable relay vault when the service is missing
A missing service threw out of the constructor, so a Vault wiring bug
would abort relay startup and take every PTY on the host with it. The
unsupported-platform branch three lines above already treats a Vault
failure as a soft disable; do the same here.
Threading the service through the two handlers instead of a field also
retires the definite-assignment assertion the throw was propping up.
* fix(ai-vault): drain consumed cache invalidations
invalidatedPaths was re-applied in every request's finally and never
drained, so once N paths had been invalidated every later request paid N
evictions for the life of the process; the 4096 cap only bounded how bad
that got.
The re-apply exists to cover a read that overlapped the invalidation, so
drain once nothing is executing. Clearing unconditionally would drop the
re-apply for a request still running on the other lane.
* fix(ai-vault): keep a busy child through slow invalidation acks
invalidate() reused the 5s ready budget as its acknowledgement deadline
and killed the child on expiry, so a delete issued during a large scan
could kill a healthy process mid-scan and burn a slot toward the restart
circuit.
Fault only when nothing is executing. Fork IPC ordering already puts the
invalidation ahead of any later request, so a busy child owes no ack
here, and the 130s/15s request deadlines still catch a wedged one.
The start-retry predicate moves to the state module to stay under the
line cap, matching the shape the relay client already uses.
* fix(ai-vault): report a failed local scan as a host issue
A local-scope scan let its error escape to the renderer, which paints it
over the session list. Service supervision now produces those errors, so
"AI Vault service restart circuit is open." replaced the list.
Route local scope through the degradation the all-hosts leg and every SSH
leg already use, so it lands as a retryable host issue row instead. Same
result shape either way, so no IPC or wire contract changes.
* test(ai-vault): cover the relay restart circuit transitions
The relay policy shipped without tests. Pin both circuit edges, the
aging-out case, the forced-refresh reopen the relay has and the desktop
does not, and the backoff schedule.
* fix(ai-vault): keep the OpenCode roots in the service child env
The scrubbed allowlist dropped XDG_DATA_HOME and OPENCODE_DB, which the child
reads to locate the OpenCode store and database. The pre-PR worker thread
inherited them, so a user who sets either lost every OpenCode session.
* test(ai-vault): anchor the service spawn env assertion
* perf(native-chat): probe WSL transcript paths asynchronously
The WSL transcript resolvers probed `\wsl.localhost` UNC candidates with
existsSync. Those paths are served over 9P, so a stopped or unreachable distro
blocked the Electron main thread instead of falling through to the next
candidate.
- host-readable-transcript-path: the `pathExists` dep is now async, defaulting
to fs/promises.access. The per-distro loop stays sequential — the ranked order
exists so the owning distro wins, and probing every distro at once would fan
9P calls out to ones the user deliberately left stopped.
- session-file-resolver: dropped the existsSync guard outright rather than
converting it. walkSessionFiles already yields [] for a missing or unreadable
root, so the guard was redundant as well as blocking.
* perf(native-chat): stop the resolve poll mkdir-ing the Codex runtime home
codexSessionsDirs() only needs the managed home's path to scan it, but called
getOrcaManagedCodexHomePath(), which mkdirSyncs. That put a synchronous fs call
back on the 500ms-5s resolve poll the surrounding async probe work just cleared,
and materialized the runtime home as a side effect of a read-only lookup.
Use the resolve-only variant the module already exposes for this case. A missing
root walks to no matches, so behavior is unchanged.
* perf(native-chat): gate WSL transcript fs access behind a shared queue
Serializes and dedupes WSL 9P filesystem probes (access/readdir) so a
stopped or slow distro can't exhaust the shared libuv threadpool or
block unrelated local filesystem work. Routes Codex session-tree scans
and path-existence checks through the new wsl-transcript-fs-gate,
while sharing in-flight scans across concurrent callers.
* perf(native-chat): prioritize exact WSL transcript probes over scans
- Add cancellation (AbortSignal) throughout session resolution and
directory walking so an unsubscribed transcript watch stops
in-flight WSL filesystem work instead of leaking it.
- Split the WSL fs task gate into exact vs scan priority lanes with
per-route concurrency, so a live transcript access probe is never
queued behind a directory scan on another distro/provider.
- Extract Codex WSL session path scanning into its own module that
shares one root snapshot across concurrent session-id lookups and
refreshes on a shared miss to see post-start file creation.
- Skip the async WSL probe entirely for local paths via existsSync.
* fix(native-chat): harden WSL transcript cancellation
* fix(native-chat): finish transcript cancellation
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>