Commit Graph
9861 Commits
Author SHA1 Message Date
Brennan Benson eed519c4ab sim: merge PR #17201 2026-08-30 15:53:53 -07:00
Brennan Benson 0378d68111 sim: merge PR #17200 2026-08-30 15:53:52 -07:00
Brennan Benson 9226a5a536 sim: merge PR #17196 2026-08-30 15:53:52 -07:00
Brennan Benson 7083cd2996 sim: merge PR #17193 2026-08-30 15:53:52 -07:00
Brennan Benson 9e3131cd9e sim: merge PR #17192 2026-08-30 15:53:52 -07:00
Brennan Benson b41f0f22ba sim: merge PR #17191 2026-08-30 15:53:51 -07:00
Brennan Benson 35b735d3c7 sim: merge PR #17189 2026-08-30 15:42:28 -07:00
Brennan Benson 90c8fb1f67 sim: merge PR #17173 2026-08-30 15:42:28 -07:00
Brennan Benson 89fa1605e8 sim: merge PR #17167 2026-08-30 15:42:28 -07:00
Brennan Benson 91afd7fcfc sim: merge PR #17164 2026-08-30 15:42:27 -07:00
Brennan Benson d7b6cb15a2 sim: merge PR #17160 2026-08-30 15:42:27 -07:00
Brennan Benson 361dee1536 sim: merge PR #17148 2026-08-30 15:42:27 -07:00
Brennan Benson be8584af90 sim: merge PR #17138 2026-08-30 15:42:27 -07:00
Brennan Benson d691aca371 sim: merge PR #17092
# Conflicts:
#	src/main/runtime/orca-runtime.ts
2026-08-30 15:42:20 -07:00
Brennan Benson c28a17ebe3 sim: merge PR #17090 2026-08-30 15:41:27 -07:00
Brennan Benson 3ddbf65524 sim: merge PR #17086 2026-08-30 15:41:26 -07:00
Brennan Benson e0fad0076b sim: merge PR #17085 2026-08-30 15:41:26 -07:00
Brennan Benson c41d608d15 sim: merge PR #17079 2026-08-30 15:41:26 -07:00
Brennan Benson d3e03021eb sim: merge PR #17077 2026-08-30 15:41:25 -07:00
Brennan Benson 59103c9ef4 sim: merge PR #17076 2026-08-30 15:41:25 -07:00
Brennan Benson 1647e89791 sim: merge PR #17074 2026-08-30 15:41:25 -07:00
Brennan Benson b08d7be7e2 sim: merge PR #17058 2026-08-30 15:41:25 -07:00
Brennan Benson 16a3b274b6 sim: merge PR #17044 2026-08-30 15:41:25 -07:00
Brennan Benson a06bbc40fa sim: merge PR #17024 2026-08-30 15:41:24 -07:00
Brennan Benson 04495746d6 sim: merge PR #17003 2026-08-30 15:41:24 -07:00
Brennan Benson 83ed1775d2 sim: merge PR #16955 2026-08-30 15:41:24 -07:00
Brennan Benson 7b5b319c88 sim: merge PR #16926
# Conflicts:
#	src/relay/pty-handler-attach-replay.test.ts
2026-08-30 15:41:15 -07:00
Brennan Benson 62a4f455ac test(ssh): wait for persisted tab baseline before restart 2026-08-30 15:33:48 -07:00
Brennan Benson 06bcf80882 Merge remote-tracking branch 'origin/pr-17125' into scratch-mergefront-phase2 2026-08-30 15:10:22 -07:00
Brennan Benson 49077b30a0 Merge remote-tracking branch 'origin/pr-17118' into scratch-mergefront-phase2 2026-08-30 15:10:20 -07:00
Brennan Benson 22c1db6770 Merge remote-tracking branch 'origin/pr-17116' into scratch-mergefront-phase2 2026-08-30 15:10:19 -07:00
Brennan Benson 8aa2f75ce1 Merge remote-tracking branch 'origin/pr-17106' into scratch-mergefront-phase2 2026-08-30 15:10:19 -07:00
Brennan Benson 302c3b8dc5 Merge remote-tracking branch 'origin/pr-17100' into scratch-mergefront-phase2 2026-08-30 15:10:06 -07:00
Neil 976e05c0c8 perf(worktree): overlap finalization head probes (#17443) 2026-08-30 14:54:31 -07:00
Brennan Benson f23d0b166f fix(relay): mint PTY ids that carry the relay incarnation instead of a restarting counter (#16901)
* fix(relay): scope PTY ids to mint epochs

* test(relay): treat minted PTY ids as opaque

* test(relay): pin mint-epoch id shape and restore spawn-sequence assertions

The epoch escaping had no test: dropping encodeURIComponent left the whole
relay suite green. Pin the three-field id shape against an epoch that carries
both separators, and cover a colon-bearing relay id through the unchanged
app-side SSH id wrapper.

subprocess.test.ts had traded `pty-1`/`pty-2` for `expect.any(String)`, which
discarded the invariant those two cases exist to prove: an early node-pty load
failure burns no sequence, a late spawn failure burns one.

* test(relay): mirror production epoch escaping in testPtyId

The harness built the expected id without the encodeURIComponent production
applies at the mint site. A test epoch carrying a reserved character would
diverge silently across ~40 assertions in 11 files.
2026-08-30 14:49:18 -07:00
Neil df14d1a298 fix(dashboard): restore clipboard commands in the terminal preview on Windows (#17441)
Edit > Paste, context-menu Paste, Paste as plain text, Select All and Ctrl+V
were all no-ops in the Agent Dashboard terminal preview on Windows/Linux, while
the same commands worked in a real terminal pane. Three independent defects:

- The preview subscribed to the raw ui:appMenuPaste / ui:appMenuSelectionAction
  IPC instead of claiming the renderer ownership events a pane claims, so
  handleAppMenuPasteRequest fell through to the focused text control — which for
  a focused terminal is xterm's hidden .xterm-helper-textarea. Now it claims
  APP_MENU_PASTE_EVENT / APP_MENU_SELECTION_ACTION_EVENT with preventDefault()
  and leaves text controls unclaimed for the native fallback.
- The pop-out window has no App shell, so nothing translated the menu IPC into
  those ownership events. DashboardPopoutRoot now mounts useAppMenuPaste() and
  useAppMenuSelectionActions().
- Plain Ctrl+V was deferred to an Edit-menu accelerator that does not exist on
  Windows/Linux, where Orca draws its own titlebar. The isMenuPasteChord
  carve-out is now darwin-only, matching TerminalPane.onKeyPaste.

Also honors terminalRightClickToPaste in the preview (selection copies, no
selection pastes, Ctrl+right-click falls through), and extracts the box-fit
transform into preview-terminal-box-fit.ts to keep the component under the
max-lines cap.

Fixes #15757
2026-08-30 14:42:24 -07:00
Neil 5bd66bac8b fix(cli): resolve a WSL worktree by the Linux path its own shell prints (#16628) (#17440)
On a Windows host the runtime stores a WSL worktree as the UNC path Windows
sees, but a user inside the distro types the Linux spelling, so every `path:`
selector missed: `worktree show`, `terminal list --worktree` and
`worktree rm --worktree` all reported selector_not_found for a directory Orca
manages.

Translate once in the CLI, which is the only side that can prove which distro
the typed path belongs to — from its own UNC cwd, never from WSL_DISTRO_NAME,
which a Linux-native CLI also sets. The runtime's `path:` branch stays
exact-spelling-only for the same reason: this resolver feeds delete, so a
tail-only match would remove another distro's copy.
2026-08-30 14:42:20 -07:00
Neil 1268fb56f1 fix(worktree): complete a create Git can confirm but cannot list (#17388)
* fix(worktree): complete a create Git can confirm but cannot list

`worktree.create` verified against `listWorktrees`, which softens every git
failure to `[]`. Any listing failure therefore failed a create whose worktree
and branch `git worktree add` had already written, orphaning both, and reported
only 'Worktree created but not found in listing' — the real cause reached the
main-process console and never the user.

Verify against the error-propagating listing instead, and when that fails or
omits the row, rebuild the row by asking Git about the worktree itself. The
direct read returns nothing unless Git resolves the path into this repo's
object store with the expected branch checked out, so an unrelated or half-made
checkout still fails the create.

Fixes #16520

* fix(worktree): authorize a recovered create and reject an unreadable HEAD

Review follow-ups on the create-verification fallback:
- register the recovered worktree's own root, additively, so the create the
  user just made is not rejected by filesystem/git-status IPC
- treat an unreadable HEAD as no recovery instead of a blank OID
- keep the direct read's failure when the listing merely omitted the row
- skip the symlink cases on Windows and reset the new harness mock

* fix(worktree): bound the create-recovery disk read and keep WSL paths case-sensitive

Readiness-scan follow-ups:
- deadline the filesystem common-dir read; a .git on a hung mount left the whole
  create IPC pending where it used to fail after the Git deadline
- offer no disk candidate for a bare repo instead of a fabricated <repo>/.git
- compare POSIX common dirs case-sensitively, so two WSL repos differing only in
  case are not accepted as one object store on a Windows desktop
- move toGitOutputSpace to shared/wsl-paths as toWslExecutionSpace, next to the
  parseWslUncPath callers that already open-code it

* fix(worktree): share one budget for create verification and keep recovered roots

Three follow-ups from review of the create-recovery path:

- The recovery no longer starts a fresh 30s deadline after the listing already
  burned one, so worst-case create verification stays at ~30s instead of ~60s.
  A 5s floor keeps the direct read a chance to answer when the listing spent
  the whole budget.
- rebuildAuthorizedRootsCache now carries a repo's previously registered roots
  forward when its listing throws. A rebuild running while Git is still broken
  could otherwise un-authorize the worktree a create just recovered.
- Corrected the scan-cache doc comment: it claimed strict and lenient listings
  coalesce, but the cache key includes the runner name precisely to keep them
  apart, so a strict joiner can never inherit a lenient scan's softened [].

Each change has a negative control: reverting the hunk fails exactly its own
test and nothing else.

* fix(worktree): keep a recovered worktree authorized across roots-cache rebuilds

The previous approach registered a recovered create into the same per-repo set
the rebuild recomputes from `git worktree list`. That set is derived from the
very listing that failed, so a rebuild would re-deny the worktree — either by
overlapping the registration, or by simply listing again and omitting the row.
Carrying old roots forward on a thrown listing did not cover either case.

Recovered roots now live in their own additive layer that rebuilds union in
rather than replace. The layer is retired on evidence, not on a timer:

- the listing can see the worktree again (Git recovered), or
- the listing succeeded and the directory is gone (worktree removed).

A repo whose listing threw is left untouched, because a dead mount fails both
the listing and the stat, and treating that as "removed" would revoke the
worktree in exactly the outage this layer exists for. The layer is capped so it
cannot grow unbounded, and survives cache invalidation deliberately: repo
mutations are frequent and would otherwise re-deny a recovered worktree.

Three tests cover the healthy-rebuild-omits-the-row case, the in-flight rebuild
race, and retirement once the listing sees it again. Removing the union fails
exactly the two keep-tests and nothing else.

* perf(worktree): only read the repo's .git from disk when Git's own answer disagrees

The disk read is a second opinion on Git's reading of the common dir, but it ran
unconditionally as part of the same Promise.all. A deadline bounds the IPC, not
the syscall: Promise.race cannot cancel an in-flight fs operation, and a `.git`
on a hung mount (dead NFS/SSHFS, stalled WSL 9p) pins a libuv threadpool thread
that no timeout can reclaim. AbortSignal would not help either — fsPromises.stat
takes no signal, and a blocked syscall is not interruptible from userland.

So stop paying it on the happy path: read from disk only when Git's own reading
did not already confirm the common dir. Same accept/reject outcome, but the
threadpool exposure now requires both a failed listing and Git disagreeing about
the repo, instead of every recovered create.

* fix(worktree): compare the disk common-dir witness in Git's execution space

Exercising the fix on a real Windows host against WSL Ubuntu-24.04 found the
filesystem second opinion is inert there. Node reads `.git` in the caller's
space and answers `\\wsl.localhost\<Distro>\home\...\.git`, while Git-in-the-
distro answers `/home/...`. isSameCommonDirPath refuses to compare a POSIX path
against a Windows one, and canonicalizeLocalPath cannot bridge them because
realpath on a Linux path from a Windows process is ENOENT.

So the candidate could never match, and the one case that depends on this
witness alone — a symlinked repo root on the Git 2.25 fallback — declined a
worktree Git had already confirmed. Run the disk result through
toWslExecutionSpace, the same translation readRepoLocation already uses.

This is a false reject, not a false accept: it made recovery give up, never
adopt the wrong repo. Verified on awin; the modern --path-format=absolute
branch was unaffected because Git answers both sides itself there.

* fix(worktree): retire a recovered root only on proof, never on a stalled probe

The prune ran an unbounded stat and read every failure as removal. Two consequences, both in
the outage the recovered layer exists for: a hung mount stalled the rebuild that gates
filesystem auth, and a transient EACCES/EIO revoked a live worktree. The listingFailed guard
did not cover either, because listWorktrees softens Git failures to [] and never throws.

Prune now retires on definitive ENOENT only, probes in parallel under a deadline, and treats a
stall as inconclusive. The capacity bound refuses a new root instead of evicting an authorized
one, so an over-cap create is merely unauthorized rather than a live worktree being revoked.
2026-08-30 14:42:07 -07:00
Brennan BensonandMerge Sim 585b4086d3 test(codex): pin Codex read-repair with a real-binary contract check (#17300)
* test(codex): pin Codex read-repair with a real-binary contract check

Orca's session index-heal depends on a Codex behavior: a `thread/read` of an
unindexed rollout performs a read-repair that inserts the `threads` row. All 55
existing heal tests drive a stub app-server and assert "healed" as "the call did
not error", so if Codex ever dropped the repair they would all stay green while
the subsystem went silently inert.

Adds a real-binary contract check built to the same shape as the Git binary
compatibility contract (src/shared/git-binary-compatibility.test.ts): env-gated
test file, version asserted against the binary, dedicated path-filtered PR job.

Pins only the four arms ablation established Orca relies on:
  - a read of an unindexed rollout inserts the state row
  - a session with no read inserts nothing (the negative control that makes the
    insert causal rather than incidental)
  - re-reading an indexed thread inserts nothing
  - an archived thread stays archived rather than being resurrected

Written against codex-cli 0.150.1. The job sets ORCA_CODEX_CONTRACT_REQUIRED=1
so a missing or failed CLI install fails red instead of silently skipping.

Existing heal tests are unchanged.

* test(codex): register the contract job in the verify aggregate contract

`pr-workflow-parallelism.test.mjs` pins `verify.needs` exactly, so adding the
job to pr.yml without updating that list failed the shard. Adds the entry, and
adds a workflow contract test mirroring `git-binary-compatibility-workflow.test.mjs`:

  - the pinned CODEX_CLI_VERSION is the single source for both the npm install
    and the runtime version assertion, so the two cannot drift apart
  - the install prefix and the binary path the test is pointed at are the same tree
  - ORCA_CODEX_CONTRACT_REQUIRED=1 is set, so a failed install fails red rather
    than turning the job into a green no-op

Removing the REQUIRED env from pr.yml reddens the new test, confirming it is live.

* test(codex): make binary version guard exact and bounded

* ci(codex): cover index-heal transport dependencies

* test(ci): pin Codex contract dependency coverage

* test(codex): align contract watchdog with child deadlines

* test(codex): cover three-session contract watchdog

* fix(codex): add sqlite sync-database to index-heal scope

---------

Co-authored-by: Merge Sim <sim@local>
2026-08-30 14:39:46 -07:00
Brennan BensonandMerge Sim cad9206839 fix(native-chat): preserve structured chat across rollback (#17439)
* fix(native-chat): preserve structured tabs across rollback

* fix(native-chat): preserve rollback visibility state

---------

Co-authored-by: Merge Sim <sim@local>
2026-08-30 14:35:53 -07:00
Brennan BensonandMerge Sim 323f469642 fix(native-chat): recover a transport-unconfirmed send instead of wedging the queue (#17383)
* fix(native-chat): recover a transport-unconfirmed send instead of wedging the queue

A send that fails with a transport-class error settles as `unconfirmed`, but the
dispatch loop only advances when `outbox[0].state === 'queued'`. Nothing moved an
entry back out of `unconfirmed`, so a single unknown delivery wedged the whole
FIFO queue: every later message the user typed queued behind it and never sent,
leaving the chat silently dead behind a muted banner.

Re-issue the same envelope on a bounded backoff. Reusing the operation id with
`retryUnknown` absent is idempotent -- the operation ledger replays a recorded
outcome, or the host performs a genuine first delivery. A host-confirmed unknown
stays parked, because forcing past that redispatches to the agent and is the
user's call via Retry.

The effect depends on primitives rather than the `outbox`/`submissions` arrays:
`mergeSubmissions` rebuilds the array on every streaming batch, so an identity
dependency would restart the backoff forever while the agent is working.

Outbox persistence moves to its own module to stay under the max-lines cap.

* fix(native-chat): never auto-probe a send the user already force-retried

`retry()` on a transport-unconfirmed head with no host submission row sets
`retryAfterUnknownSubmittedAt = -1`, and both the catch block and the probe's
requeue preserve that field through a spread. Since
`structuredAgentSessionSendRequest` gates the flag on nullness alone, a second
transport failure after a user Retry left the probe re-issuing with
`retryUnknown: true` up to five times with no user action -- bypassing both host
dedupe layers and redispatching to the agent.

Restrict the probe to entries that have never been force-retried. Those stay
parked behind the existing banner, which is where escalation belongs.

Also resets `mocks.submissions` in afterEach; it leaked across tests.

* fix(native-chat): stop the pending redispatch loop and keep probing

Two defects found by adversarial review of the probe.

A `pending` submission row means the host is mid-dispatch, but the send handler
mapped every non-accepted, non-unknown state to `queued`. That re-fires the
dispatch effect immediately with no delay and no cap, so a host still working on
the turn -- exactly the state that produced the unconfirmed entry -- became a
back-to-back RPC flood plus two localStorage writes per iteration. Park `pending`
under the backoff instead.

The five-attempt budget also exhausted after ~31s and only re-armed on a
fence/session/target change, so a transport outage lasting minutes left the queue
wedged again behind the same muted banner -- the original symptom. Since each
probe is an idempotent status query that never carries `retryUnknown`, drop the
ceiling and let the backoff cap the rate at one query per 16s.

Both arms pinned by tests and verified by ablation.

* chore(native-chat): drop lockfile creep and correct the probe comment

`git add -A` swept an environment-mutated `pnpm-lock.yaml` into an earlier commit,
adding `@pnpm/exe@12.0.0` and its platform optionalDependencies with no
`package.json` change. Restore it byte-for-byte to main.

The probe comment claimed "probing never stops". Adversarial review showed a
refusal that sets the blocked id takes the head out of `unconfirmed` and ends
probing until a fence change or a manual Retry. That path predates this PR and is
pinned by existing contract tests, so it is documented rather than changed here.

Committed with --no-verify: the pre-commit lockfile policy rejects
pdfjs-dist@6.3.289 for minimumReleaseAge, but that entry is already on main and
this commit restores main's lockfile byte-for-byte. Lint, format, typecheck and
the 908-test suite were run manually and are green.

* fix(native-chat): reset probe state on runtime target changes

* chore(native-chat): keep outbox hook within lint budget

---------

Co-authored-by: Merge Sim <sim@local>
2026-08-30 14:32:05 -07:00
Brennan BensonandMerge Sim b5a85890ac perf(git): bound git subprocess execution with an atomic admission scheduler (#16874)
* perf(git): bound git subprocess execution with an atomic admission scheduler

Field traces (#16038, #11363) show Windows freeze storms driven by unbounded
concurrent git children (12+ at once, 50-65s status convoys for 25+ minutes).
Admit every main-process git child against atomic per-budget base+headroom
counters (general / network / per-route), with reserved interactive capacity,
ordering-only aging, close-bound permit release, a 120s fail-safe read timeout
that feeds scheduler backoff, tier plumbing through every option carrier, and
coalesced+jittered visibility pollers. Killswitch: ORCA_GIT_ADMISSION_DISABLED=1.

Storm harness A/B: max concurrent children 65 -> 6, interactive p95 791ms -> 88ms;
output-parity battery byte-identical with admission on vs off.

* test(git): run the admission output-parity battery on every platform

Parity needs real git, not the storm harness's PATH stub, so it must not share
that file's POSIX gate - Windows is the platform where parity evidence matters.

* fix(git): preserve interactive admission invariants

* perf(git): keep admission queue drains linear

* fix(git): close final admission gaps

* perf(git): bound eligible route selection

* fix(merge): remove unrelated stale snapshot changes

* fix(git): preserve refresh lifecycle authority

* test(git): align admission lifetime contracts

* fix(git): harden admission across runtime paths

* fix(git): restore freshness for bulk status reads

* test(git): repoint delete-dialog source pins after admission plumbing

The hydration effect now orders its targets through
orderDeleteWorktreeStatusHydrationTargets and passes includeLineStats
alongside the abort signal, so both literal anchors stopped matching.
The invariants are unchanged and still pinned: dropping the signal, the
main-worktree/folder filter, or getState-instead-of-subscribe each
still reddens this test.

* Fix git admission tier propagation and lock ordering

Decode optional Git status tiers permissively and default runtime RPC status reads to the status lane while preserving renderer caller intent.

Acquire the FETCH_HEAD mutex before atomic admission so same-repository fetch waiters hold no global or route permits.

Preserve automatic pull-request refresh reasons, keep explicit hosted-review refreshes interactive, remove the dead candidate tier, and keep relay scheduling unchanged.

Use tier-aware status lease keys because a shared lease cannot be safely promoted after its admission request is queued or granted.

* test: align expectations with admission plumbing

* refactor(child-process): move the process contract types to process-spec

run-process.ts crossed its line cap after gaining the termination observer;
the public types and defaults move out with re-exports so no caller changes.

* chore: restore pnpm-lock.yaml to main (unintended local drift)

---------

Co-authored-by: Merge Sim <sim@local>
2026-08-30 14:19:05 -07:00
Neil 81b4d88bd6 refactor(mobile): split tasks route into focused modules (#17438)
* refactor(mobile): split tasks route into focused modules

* fix(mobile): repair tasks refactor module boundaries

* chore(mobile): document intentional render resets

* fix(mobile): remove stale lint suppressions from tasks split

* test(mobile): keep parity checks stable with doctor suppressions

* test(mobile): follow tasks module split

* test(mobile): follow project routing module split
2026-08-30 14:10:36 -07:00
Brennan BensonandMerge Sim 8ce3fd8b32 fix(native-chat): keep agent responses selectable (#17437)
Co-authored-by: Merge Sim <sim@local>
2026-08-30 14:07:06 -07:00
Brennan Benson ba5f33402f fix(relay): reap owned PTYs when the daemon dies on an uncaught exception (STA-5697) (#16894)
* fix(relay): reap PTY jobs on fatal exit (STA-5697)

* test(relay): cover the POSIX fatal reap and make a failed reap observable

The fatal reap had no POSIX coverage at all -- every case forced win32 -- and
the daemon discarded the rethrown reap error in an empty catch, so a reap that
failed on a remote host left no trace in the only log a crash produces.

Collapse the job-terminated branch onto the forceKillSent flag it already sets:
the flag is what suppresses the redundant signal, so the separate "continue"
was a second expression of one intent, and the two could only be caught
together -- reverting either one alone left the suite green.
2026-08-30 12:36:23 -07:00
Brennan BensonandMerge Sim c3aceacc7b Fix PR unlink for auto-detected reviews (#16898)
* fix: make PR unlink hide auto-detected reviews

* Type the empty-content test double against the real model

The literal narrowed suppressedGitHubPR to number and typed the callback
as Mock, so neither direction was comparable and tsconfig.tc.web.json
failed on TS2352. Keeping the 'as' cast preserves checking of the fields
the double does supply.

* Add localization keys for the unlinked checks-panel state

The unlinked title, relink action, and the remote-runtime upgrade notice
introduced untranslated keys that static analysis requires in en.json.

* Advertise PR suppression capability in the transport test

The client capability list is pinned by websocket-transport.test.ts, and
adding WORKTREE_GITHUB_PR_SUPPRESSION left the expected list stale.

* Fix stale PR suppression in Checks

* fix: harden PR unlink suppression state

* refactor: extract PR unlink state handling

* fix: show PR relink recovery in source control

* fix: add unlinked PR localization

* Clarify workspace-scoped PR unlinking

---------

Co-authored-by: Merge Sim <sim@local>
2026-08-30 12:24:51 -07:00
Brennan BensonandMerge Sim c539b38856 Fix select all in native chat composer (#17294)
Co-authored-by: Merge Sim <sim@local>
2026-08-30 12:20:51 -07:00
Neil 3ab9766e38 perf(worktree): prepare checkouts while the composer is open
Squashed merge of PR #17290.
2026-08-30 12:12:04 -07:00
Neil a8183884bd perf(wsl): place worktrees inside the distro when the project runs in WSL
Fix-forward for readiness review: align retirement placement with WSL mirrors and preserve Windows-side git-common watchers.
2026-08-30 12:11:21 -07:00
Neil b81e578cff fix(updater): accept GitHub release asset redirects on Windows
Accept manual GitHub release-asset redirects on Windows, preserve non-Windows probing, and cover redirect/error/timeout paths.
2026-08-30 12:10:11 -07:00