mirror of
https://github.com/stablyai/orca.git
synced 2026-09-27 00:02:37 +00:00
d7123591cebd103658c6d5c8f601eebe1dc0cb3e
1837
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d7123591ce |
perf(git): pack the loose refs Orca's own fetches leave behind (#17857)
* perf(git): pack the loose refs Orca's own fetches leave behind Orca strips git's auto-maintenance off every fetch it issues (GIT_FETCH_SKIP_AUTO_MAINTENANCE_CONFIG_ARGS) and never compensated, so nothing in an Orca-driven checkout ever packs refs. One real machine reached 36,574 loose refs, where `git show-ref -- main` costs 5.2s and every worktree create pays for it. Add an idle-time, per-repo `git pack-refs --all --prune`, armed by the fetches that create the debt. It runs only after ten minutes of quiet on that repo, only above 1000 loose refs (probed with a walk bounded by that threshold, not by the backlog), one at a time across the whole app, at the background admission tier, and never while an agent is working, a create is prepared or in flight, a worktree removal is deleting refs, the app is quitting, or the machine is on battery. A user who set `maintenance.auto=false` or `gc.auto=0` has opted out. Measured on a 36,001-loose-ref fixture (macOS/APFS, git 2.44): `show-ref` 5.5-12.2s -> 30-49ms, `for-each-ref` 4.0-10.8s -> 43-48ms. Also fixes a pre-existing bug the split exposed: `--path-format=absolute` is ignored before git 2.31, and taking rev-parse's stdout raw collapsed every repo on such a host onto one fetch-serialization key. Refs #17828 * perf(git): make idle ref maintenance preemptible and cheaper to probe The idle veto was one-directional: it stopped a pack from starting during a create, removal, or agent work, but nothing stopped those from starting during a pack. A user-clicked Fetch, a branch delete, or a worktree removal that needed `packed-refs.lock` mid-rewrite could fail with `unable to create packed-refs.lock` -- a git error with no visible cause. Make the pack cancellable end to end. An AbortSignal now reaches the `pack-refs` child and both pre-pack probes, and `pause()` aborts what is running, waits for it to actually stop, and holds a suspension count so nothing new starts until the caller releases. Every entry point that deletes a ref takes that pause: gitFetch, gitPull, gitFastForward, removeWorktree, forceDeleteLocalBranch, prepareWorktreeCreateCheckout, addWorktree. Five more triggers close the rest of the window: battery drop, window focus, quit, the attempt deadline, and any other git command queueing for an admission slot. Judge a pack by re-probing the backlog rather than by the child's exit code. Measured in the field: another Orca session moved a branch mid-pack, git reported `cannot lock ref`, skipped that ref and packed the rest -- 36,688 loose refs down to 3. On a machine running several sessions that is the normal case, and retrying it would be wrong. Probe with one batched `readdir` per directory instead of streaming `opendir`, which issues a thread-pool round trip every 32 entries: 177ms -> 23ms on a real 36,600-ref repository, with half the event-loop lag. The walk stays strictly sequential so it can never occupy more than one of libuv's four filesystem threads. `PackRefsLockOwnership` makes a lock left by SIGKILL attributable, and only reclaims one when a marker exists, the lock is older than any pack-refs could run for, and the recorded process is gone. Refs #17828 * fix(git): wait out the packed-refs lock instead of killing the pack Measured on Git 2.55/APFS with 37k loose refs: a full `pack-refs --all --prune` takes 23-32s but holds `packed-refs.lock` for only 0.03-1.37s of it. The other ~95% is the prune phase, during which a concurrent `fetch --prune`, `branch -D` or `update-ref` succeeds every time -- per-ref locks last microseconds and git retries for `core.filesRefLockTimeout`. So the abort-on-everything design was strictly harmful. SIGTERM into the prune loop strands an empty `refs/**/*.lock` about one time in five (9/30, 5/40, 6/30 kills): `tempfile.c` opens the lock O_EXCL before `activate_tempfile()` links it into the list the signal handler walks, and a pack does ~36k lock cycles. Afterwards `update-ref -d` on that ref fails with `cannot lock ref ... File exists`, permanently. On Windows `taskkill /f` never runs git's handlers at all, so an abort inside the rewrite strands `packed-refs.lock` every time. Never signal the child. `packRefs` no longer takes an abort signal; it polls `packed-refs.lock` and reports the window through a `PackedRefsLockReporter`. `pause()` resolves when the lock is released -- bounded, and free during the prune -- while the suspension counter still blocks new attempts. Battery and window-focus become do-not-start rather than stop-what-is-running, and quit waits for the lock and lets the child finish orphaned. For strands that already exist, `PackRefsLockOwnership` now also reclaims `refs/**/*.lock` under the same three conditions plus a 0-byte check, and a lock carrying our own not-yet-reclaimable marker records `locked` with a 30min retry instead of the 6h failure cooldown -- so a Windows strand self-heals in half an hour rather than six. Reverts the git admission-scheduler event bus, which existed only to drive the abort this removes. Refs #17828 * test(git): make the ref-maintenance waits survive a loaded runner CI shard 4/8 failed on `restarts every armed countdown when the user does ref work themselves`, which passes locally. The `until()` helper spun a fixed 200 event-loop turns and then returned silently, so on a contended runner the filesystem probe had not finished and the assertion that followed failed with an unrelated message. Bound the wait by wall clock instead and throw a named error, which immediately exposed a second latent bug: the single-flight test's second wait could never succeed, because the deferred repo's retry is on a faked `setTimeout` that spinning the real loop never advances. It had been passing only because the old helper gave up quietly. Add a timer-aware variant for those, and have the countdown test await a signal the fake pack resolves rather than polling at all. Verified stable across five sequential runs and once under load average 32 with six concurrent suites. Refs #17828 |
||
|
|
fdfe354045 |
test(relay): bind test WebSocket servers to loopback
A control-handshake test that expects a timeout was instead getting
'Unexpected server response: 401' about once in fourteen runs. A slow machine
cannot turn a timeout into a 401 -- that needs a real HTTP response, so the
connection was reaching a different server.
new WebSocketServer({ port: 0 }) binds the wildcard address while the client
dials 127.0.0.1. On macOS those differ, and with SO_REUSEADDR a foreign process
can hold the more specific 127.0.0.1:P and win the connection. Caught live: a
wildcard bind took port 52584, which a running Orca app already held on
loopback, and Orca answered the probe. A listener that checks a token answers
401.
Ten constructions across seven files now pass host: '127.0.0.1', so the
reservation covers the address the client dials and a duplicate bind is refused.
Adds a ratchet, because this is not authors forgetting a convention: all 30+
.listen(0, ...) sites already pass '127.0.0.1', while 7 of 7 ws constructions
did not. ws accepts { port } alone and binds the wildcard silently, so nothing
told them. The guard pins the wildcard count, and pins separately at zero the
option shapes it cannot read -- spreads and variable option objects fail rather
than being exempted, and a recognized-construction floor catches the matcher
going blind, which otherwise reads exactly like a clean tree.
mobile/scripts/mock-server.ts stays on the wildcard deliberately: a phone
reaches it over the LAN.
|
||
|
|
3f5c54332d |
fix(github-project): sort and group empty field values last in both directions
compareSort early-returned 1 for a missing value — before the trailing DESC flip — but expressed the same idea as `cmp = 1` for an empty users/labels list, which that line then negated. Descending order therefore scattered empty cells across both ends of the table. getFieldValueForGrouping had the matching defect: an empty list fell through to deriveStringValue and produced a blank-label group that the header renders as the literal "All". Both paths now share one predicate, which also covers `text: ''` and `date: ''` — reachable because the view normalizer maps a null GitHub text/date to the empty string. Co-authored-by: kaluli123123 <295758798+kaluli123123@users.noreply.github.com> |
||
|
|
398aeccdfe |
fix(worktrees): retire runtime-host metadata a scan proved gone
A paired client's WorktreeMeta for a runtime host is exempt from gcStaleWorktreeMeta -- that GC skips any row that is not local on both the repo and the meta's hostId -- so a scan-proven removal is the only thing that ever retires one. Both halves of that path were gated to `ssh:`, so the client kept a row for every remote worktree it had ever seen and dropped none. The renderer already computed the removals for runtime hosts and purged its own in-memory state with them; only the persisted half bailed. Widen it, and the matching main-side handler, to runtime hosts. `OffHostExecutionHostId` names the set precisely: the hosts the local-only GC skips. Also require `source === 'git'` before retiring anything. `session-fallback` reports `authoritative: true` but is the truncated, visibility-filtered `worktree.list` reply from a host too old for `worktree.detectedList`; its omissions are no evidence a checkout is gone. That guard did not matter while this only ran the in-memory purge, and does now that it deletes rows. A repo that reaches its checkouts over a connection is still never condemned under a runtime host id -- the host that executes owns that verdict. Refs #17776 |
||
|
|
80a52bb9b3 |
fix(git): recover commit ref badges on Git older than 2.43 (#17923)
GIT_HISTORY_COMMIT_FORMAT asked for decorations with %(decorate:…), which Git 2.43 introduced. Older Git prints the placeholder verbatim and exits zero, so nothing raised and every commit in the Source Control panel silently lost its branch, remote and tag badges. The record now also carries %D (Git 2.10) on its own line, selected by an exact match against the unexpanded placeholder — a ref name can never contain the \x1f that Git expands inside the echoed text. %n emits the %D line on both sides of the boundary, so the message index is fixed and a missed match degrades to no badges rather than a corrupted message. The decoration separator is now bound to the field that produced the text instead of sniffed from it. A lone decoration carries no separator, so the old sniff split `refs/heads/feat,one` into two bogus refs. Verified against real Git 2.38.1 and 2.49.1. Co-authored-by: kaluli123123 <295758798+kaluli123123@users.noreply.github.com> |
||
|
|
2c559fa96a |
test(child-process): make the import ratchet able to fail
never grows asserted offenders.length <= ALLOWLIST.length, but the two membership assertions already force those equal, so it could not fail. The comment claimed it caught a swap -- one file migrated off child_process, one added -- which is exactly the case it let through. Pins the true count and asserts both directions, so a swap fails and a pin left stale-high after a migration also fails rather than banking ground twice. Gives the console-visibility ratchet the same test: it had no count assertion at all and the same gap. Also anchors the owner-directory exemption with a trailing slash, so a future src/shared/child-process-foo.ts is scanned rather than silently exempt. |
||
|
|
8cc7634051 |
refactor: name modules for their domain instead of 'helpers'
Renames seven -helpers modules for the concept their functions operate on, and splits three that were genuine grab-bags -- each had a clean cleavage along its importers, which is the signal AGENTS.md describes for a file holding more than one responsibility. Leaves keybindings/definitions-core-1..4 alone: definitions.ts spreads them in order, so their concatenation order is the command palette order and regrouping them thematically would be a user-visible change. Records that reasoning in a comment so it is not re-litigated. |
||
|
|
9542b45d99 |
fix(wsl): resolve conflict and working-tree probes in the host path namespace (#17895)
Git running inside a WSL distro writes `.git` gitdir pointers, and answers
`status --porcelain`, in the guest namespace. Node reads both back in the
Windows main process, where `/mnt/c/repo/.git` resolves to `C:\mnt\c\repo\.git`
and `/home/me/wt` names nothing at all. Four fs probes were built on those
fabricated paths and always came back "absent":
- `detectConflictOperation`'s four marker probes, so merge/rebase/cherry-pick
badges silently went missing.
- `parseUnmergedEntry`'s compat existence check, so every `deleted_by_us` /
`added_by_them` conflict rendered as 'deleted' regardless of the working tree.
- `findExistingWorktreeSymlinkPaths`' `lstat` from status, so Orca's own shared
symlinks (node_modules and friends) showed as user changes.
- the same `lstat` from the hosted-review dirty preflight, which fails closed:
an unreadable shared symlink read as uncommitted work and blocked PR/MR
creation outright.
`resolveGitDir` computes the host spelling of the worktree once and uses it for
both the gitfile read and the pointer resolve, so a guest-spelled worktree path
is reached at all, and a relative pointer (`worktree.useRelativePaths`, git
2.48+) resolves against a spelling Win32 understands. The pointer itself now
goes through the already-landed `resolveGitMetadataPath`, and the function gains
an optional `{ wslDistro }` for a caller whose base path does not encode a
distro. `detectConflictOperation` forwards it, and the three callers that reach
it -- status-read, the runtime RPC, the `git:conflictOperation` IPC -- pass the
git options they already hold. The return type stays `Promise<string>`.
`resolveWorktreeHostPath` is the same rule applied to a worktree path, used by
status-read for the two working-tree probes and by the review preflight. Both it
and `resolveGitMetadataPath` now treat only a single-leading-slash path as guest
namespace: `//wsl.localhost/...` is already a host UNC spelling, and translating
it prepended a second share prefix.
`readWorktreeDiffStamp` needed the same one-namespace guarantee, since moving
translation inside `resolveGitDir` would otherwise make its HEAD and index real
while the working-tree stat stayed fabricated, letting a settled diff survive
every edit. #17896 landed that change first, so it is no longer in this diff;
its version is a superset and all four components already resolve from one
`hostWorktreePath`. What remains here is the `resolveGitDir` gitfile-pointer
fix that #17896 explicitly deferred, which `worktree-diff-stamp-host-paths.test.ts`
pins.
`getConflictCompatibilityStatus` moves from `existsSync` to async `access`, for
the same reason `detectConflictOperation` did: once these paths are real they
are `\\wsl.localhost\...` shares, and a sync probe per asymmetric conflict
blocks the Electron main thread for a 9p round trip on every status poll.
Per-platform delta:
- native Windows, no WSL: no behavioral change. Nothing here starts with a
single `/`, so no path is translated. An absolute pointer is now returned
verbatim rather than separator-normalized; every consumer re-joins or
normalizes it before use.
- macOS/Linux: no change. Guest-pointer translation is gated to win32, and a
caller-named distro is ignored off Windows.
- Windows + WSL: drvfs pointers and drvfs-spelled worktrees now resolve to their
drive spelling instead of `C:\mnt\...`; a non-drvfs guest path resolves
through the named distro's UNC share, or stays verbatim (ENOENT -> existing
fail-safe) when none is named.
- SSH/relay: none. Those paths return before any of this via the provider
branch; `src/relay/git-handler-status-ops.ts` keeps its own resolveGitDir.
- folder workspaces, GitLab: none. Neither is on these code paths.
|
||
|
|
899304d515 |
Increase artifact content size limit from 5 MiB to 10 MiB (#17910)
Doubles the maximum UTF-8 bytes accepted for manually shared artifacts, enabling users to share larger content while maintaining recovery and transport constraints. |
||
|
|
2b6c14d4b5 |
Add startup delivery diagnostics and success announcements (#17814)
Terminal sessions now report startup command delivery details (whether written, presence, length, and delivery method) without logging the command text—preventing credential leakage and distinguishing missing commands from lost ones in diagnostics. Setup scripts now announce completion on both POSIX and Windows before executing the startup command, so healthy setups don't appear stuck in the UI with "Waiting for setup..." as the last visible line. Diagnostics failures are caught and ignored so they never break session creation. |
||
|
|
dff2ff0ec3 |
fix(git): read the diff working tree and stamp through the host path spelling (#17896)
Git can execute inside a WSL distro against a raw Linux worktree path while Node, on the Windows side, reads the same files back through Win32. `path.join( '/home/me/repo/feature', 'src/file.ts')` on win32 produces the drive-relative `\home\me\repo\feature\src\file.ts`, which resolves against whatever the current drive happens to be and almost always ENOENTs. The same mis-spelling hits the drvfs form, where `/mnt/c/repo` should read as `C:\repo`. Two consequences, both on the Node side only (git already works, because it gets the Linux path as its cwd and resolves it inside the distro): - getDiff's unstaged working-tree read missed, `readWorkingTreeFile` mapped ENOENT to `exists: false`, and an existing file rendered as DELETED in the diff view. - `readWorktreeDiffStamp` could not find `.git`, so the stamp was null, the settled diff cache neither hit nor stored, and every diff respawned `git show` - two `wsl.exe` spawns the cache exists specifically to avoid. Both now spell the worktree directory for the reading host first, via a new `resolveWorktreeHostPath` wrapper around the resolver that landed in #17804. The wrapper exists because `resolveGitMetadataPath` trims: a gitfile payload carries a trailing newline, but a directory name may legally begin or end with whitespace on POSIX, so the wrapper keeps the caller's spelling whenever the resolver only trimmed it. The stamp's opaque `value` still embeds the caller's original `worktreePath`, so settled-cache identity is byte-identical and no cache key moves. `readWorktreeDiffStamp` was already `Promise<WorktreeDiffStamp | null>` with one caller that treats null as a cache miss, so no new nullability enters the type system and the resolver's never-null-for-a-non-empty-pointer contract is untouched. The only unspellable input is an empty worktree path, handled locally as "not provably unchanged" in the stamp and as a read *failure* (not a proven deletion) in file-diff. What changes for users | Platform | Delta | |---|---| | macOS | No change. An absolute POSIX path is returned verbatim, including one whose directory name carries leading or trailing whitespace. | | Linux | No change. Same reason. | | Native Windows (no WSL) | No change. A `C:\...` or `\\server\share\...` path is already absolute for win32 and passes through verbatim. | | Windows + WSL, UNC worktree path (`\\wsl.localhost\Ubuntu\...`) | No change. Already absolute for win32; passes through verbatim. This is today's common case. | | Windows + WSL, drvfs worktree path (`/mnt/c/repo`) | Fixed. Reads as `C:\repo` instead of the drive-relative `\mnt\c\repo`. Needs no distro name. | | Windows + WSL, Linux worktree path with a named distro (`/home/me/repo`) | Fixed. Reads as `\\wsl.localhost\Ubuntu\home\me\repo`. The deleted-file misrender goes away and the diff cache starts hitting. | | Windows, POSIX path, no distro and not a drvfs mount | No change. Passes through verbatim, same ENOENT, same existing fallback. | | SSH | No change. `runtime-git-diff-commands.ts` and the `git:diff` IPC both route to `provider.getDiff` for a connection, so this local code is never reached. | | Relay / remote | No change. No RPC param, wire field, stream opcode, or published content is touched; the relay host runs the same local code and gets the same fix. | | Folder workspace (non-git) | No change. `.git` is absent either way, `resolveGitDir` returns the same fallback, and the stamp stays null exactly as today. | | GitLab / other providers | Not applicable. No provider-specific or review code is touched. | What this does NOT do - It does not fix `resolveGitDir` itself. For a drvfs repo whose worktree Orca already spells `C:\repo\feature`, the gitfile payload `gitdir: /mnt/c/repo/.git/ worktrees/feature` is still mis-resolved by `path.resolve` to `C:\mnt\c\repo\.git\...`, so the stamp still returns null in that shape. Separate change, separate PR; this one neither fixes nor regresses it. - It does not touch submodule path resolution. `resolveSubmoduleWorktreePath` is the path-escape guard and has a near-identical twin in the relay; changing it without escape tests on both is out of scope. - It does not change `readHeadComponent`'s `commondir` resolution. The relative `../..` git actually writes takes the identical `path.resolve` branch, and an absolute POSIX `commondir` under a WSL UNC `gitDir` already resolves correctly because the UNC root is `\\wsl.localhost\<distro>\`. - It does not reorder drvfs-before-UNC inside the shared resolver. That changes the identity of returned strings and needs a real Windows+WSL box. - It does not add any Git command, option, or version dependency. Costs and residual risk - One extra pure function call per diff read. No I/O added or removed on the unaffected paths. - Translation still trims. `resolveWorktreeHostPath` preserves whitespace only when no translation happened; a guest directory named `/home/me/repo ` loses its trailing space on a Windows reader. Reachable only on win32, where such a name is not addressable anyway, and the previous behavior for that shape was a drive-relative miss. - A relative worktree path (no caller passes one) is now resolved against the process cwd instead of joined relative to it. Same file in every case except a relative name that itself ends in whitespace. - `UNSPELLABLE_WORKING_TREE_READ`'s `exists`/`failed` fields are correct but not observable today: the stamp is null for the same input, so nothing can be cached and `reusable` cannot be read back. They are there so the branch stays right if `loadDiff` ever gains a second caller. The test pins the observable part - that no read lands on a cwd-relative path. - Every test here mocks `node:fs/promises` and spoofs `process.platform`. They prove which path string reaches `stat`/`readFile`, which is the right assertion, but none of this has executed against a real 9p mount on a Windows+WSL box and this repo's CI has no such runner. - Honest framing of the trigger: I could not demonstrate a mainline path that hands `getDiff` an untranslated POSIX worktree path on Windows today - `translateWslOutputPaths` UNC-translates worktree paths whenever a distro is known, `getWslHome` returns the UNC spelling, and `resolveWslRepoWorktreeBasePath` normalizes a configured Linux base. The drvfs case is the most plausible live one. Treat this as defense-in-depth that is a strict no-op on every configuration above except the two marked Fixed. Verification - `npx vitest run src/main/git src/shared/git-metadata-path.test.ts` -> 196 files / 2241 tests passed, 2 files and 5 tests skipped. One failure, `git-admission-storm-measurement.test.ts > reports bounded-concurrency before and after measurements` (ENOENT scandir on its own temp state dir), is pre-existing and environmental: it fails identically in isolation and spawns real git children without touching any changed module. - `npx vitest run src/main/git/status-diff-settled-cache.test.ts` -> 21/21 (16 pre-existing, 5 new). `npx vitest run src/shared/git-metadata-path.test.ts` -> 25/25 (19 pre-existing, 6 new cases across 3 new tests). - `npx oxfmt --write` then `npx oxlint` on all five changed files -> clean. Mutation checks - all eight production substitutions were reverted one at a time and the suite re-run. Each fails at least one test, and no new test survives its own mutation: | Reverted | Failing test | |---|---| | file-diff working-tree read -> `worktreePath` | reads the working tree through the host spelling instead of reporting a deletion; invalidates when the working tree file is edited under the host spelling | | stamp working-tree component -> `worktreePath` | invalidates when the working tree file is edited under the host spelling | | stamp `.gitmodules` stat -> `worktreePath` | invalidates when .gitmodules appears under the host spelling | | stamp `resolveGitDir` -> `worktreePath` | stamps through the host spelling so the second read does not respawn git | | `options` threading at the `readWorktreeDiffStamp` call | stamps through the host spelling...; invalidates when .gitmodules appears... | | wrapper's untrimmed preservation -> return the resolver's value | keeps whitespace that belongs to the directory name (both cases) | | `UNSPELLABLE_WORKING_TREE_READ` -> a cwd-relative `readWorkingTreeFile` | reads nothing relative to the cwd when the worktree path has no host spelling | | stamp's null early return -> `hostWorktreePath ?? worktreePath` | reads nothing relative to the cwd when the worktree path has no host spelling | The settled-cache tests seed the fake filesystem through the platform-bound `path` module rather than `path.win32`, so they assert real behavior on a POSIX CI host as well as on Windows and are not gated on the host platform. Co-authored-by: Neil <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
e4a708da17 |
fix(wsl): read untracked line counts through the host's worktree spelling (#17897)
Untracked line counts, and the untracked share of the branch line total, come
from direct lstat/open calls rather than from git. When git executes inside a
WSL distro the worktree path can be a guest path, which Win32 reads as
drive-relative (`/home/me/repo`) or as a literal `C:\mnt\c\repo`. Every lstat
then fails, countFileAdditions swallows the error into `{}`, the file renders
with no +N, and the branch total silently undercounts it.
Route only those two filesystem reads through resolveWorktreeFilesystemPath,
which translates a guest-rooted path via the shared resolveGitMetadataPath.
Both call sites produce the same string, so the stat-keyed untracked cache
still hits across them. resolveGitMetadataPath itself is not modified, so its
other callers are untouched.
The wrapper translates only when the host is win32 and the path is a guest
spelling: exactly one leading slash, and unchanged by trim(). Each condition
is load-bearing.
- `//wsl.localhost/...` and `//wsl$/...` are UNC spellings that also start with
`/`, and translating one prepends the distro root a second time, so a
worktree that reads fine today would ENOENT on every untracked file. The same
single-leading-slash guard is used, for the same reason, by
resolveWslRepoWorktreeBasePath in src/shared/wsl-paths.ts.
- resolveGitMetadataPath returns `rawPath.trim()`, so a worktree directory name
with leading or trailing whitespace (legal on ext4) would be re-spelt onto a
different directory. Leaving those verbatim keeps them exactly as they read
today.
- Off win32 the resolver is already an identity for these inputs; the platform
check makes the macOS/Linux no-op structural rather than derived.
Per platform:
- Windows + WSL, worktree spelled as a guest path: untracked +N appears and the
branch total includes it.
- Windows + WSL, worktree already spelled `\\wsl.localhost\...`,
`//wsl.localhost/...` or `//wsl$/...`: unchanged, returned verbatim.
- Native Windows: `C:\...` is unchanged. One shape does change: a
`/mnt/<drive>/...` worktree now resolves to `<Drive>:\...` instead of being
passed through to a guaranteed lstat failure. Native Windows does not produce
that spelling, and if it were reached the new result is the correct file.
- macOS / Linux: unchanged; not win32, returned verbatim, whitespace included.
- SSH / relay: unchanged. Remote status runs in the relay, which builds its own
branch-total input and does not pass filesystemWorktreePath.
- Folder workspaces / GitLab: unaffected, no workspace-kind or provider
behavior is touched.
No fail-closed degradation: attachLineStats still returns
`stagedStats !== null && unstagedStats !== null`, and createBranchLineTotalInput
still has no early return. The wrapper returns `string`, never null, so an
unmappable worktree keeps its old spelling and its old (missing) untracked
counts rather than dropping the staged/unstaged counts as well.
The `filesystemWorktreePath` field on computeGitBranchLineTotal is optional and
does not touch the lease key, so the coalescing/cooldown identity is unchanged.
Co-authored-by: Neil <neil@orca.local>
|
||
|
|
0443d42638 |
fix(git): give WSL-hosted repos one FETCH_HEAD lock lane (#17898)
The fetch lock key is the worktree's resolved common Git directory. On a Windows host two derivations split one repo into several lanes, so sibling fetches on the same repo race the FETCH_HEAD write the lock exists to serialize. - Windows aliases \\wsl$ to \\wsl.localhost and folds the distro name and any drvfs tail case-insensitively, so two spellings of one repo produced two keys. The finished key now goes through foldWslUncPathCaseInsensitiveParts. This is a pure function of the finished key, so equal keys stay equal: it can only merge. - Under a drive-spelled base, git-in-WSL's `/mnt/c/repo/.git` gitfile and commondir pointers are read by path.resolve as the non-existent C:\mnt\c\repo\.git. The commondir read then fails and every linked worktree got its own dead-end key while the main checkout keyed on C:\repo\.git. Such a pointer now goes through toWindowsWslDrivePath. - realpath and stat take no AbortSignal, so cancelling a fetch still blocked behind a hung 9P/UNC lookup. Both are wrapped in waitForPromiseWithSignal. The rejection keeps today's synthetic AbortError shape, including when the caller aborts with its own reason, because callers classify on error.name. - The hand-rolled gitfile regex is replaced by the shared parseGitdirMarkerPayload, matching git's own read_gitfile_gently. A WSL UNC base is deliberately excluded from the pointer translation. win32 path.resolve already carries such a base's distro onto a guest-rooted pointer (\\wsl.localhost\Ubuntu\home\me\wt + /mnt/c/repo/.git -> \\wsl.localhost\Ubuntu\mnt\c\repo\.git), and a main worktree's `.git` is a directory with no pointer to translate, so its key stays on that UNC spelling. Rewriting only the linked worktrees to C:\... would have split one repo across two lanes - the opposite of the intent. A test now pins that layout. Direction of every key change, re-derived over a ten-layout matrix that runs both this code and an emulation of the pre-change derivation under Win32 path rules: nine layouts either merge or are byte-identical. The fold is merge-only by construction; the pointer translation's sole delta is C:\mnt\c\X -> C:\X, and C:\mnt\c\X is derived from bytes that live at C:\X, so it can only join a worktree to its own common dir. The tenth layout is the one narrowing: the shared parser accepts `gitdir:` only at offset 0 where the old regex accepted it on any line, so a `.git` file with a leading blank line falls through to the parent walk (C:\repo\.git\FETCH_HEAD -> C:\.git\FETCH_HEAD). Nothing can race there - git 2.44 refuses that same file with `fatal: invalid gitfile format`, so no fetch runs in such a worktree at all. Native Windows repos with no WSL, SSH, relay and folder workspaces are byte-identical. hostPath() returns the same node:path submodule Node itself selects, so it is a no-op on every real host; it exists so the Win32 derivation is testable off Windows. resolveGitFetchHeadCommand's argument parsing is untouched, and --git-dir gitfile dereferencing is deliberately not added: it would make N worktrees of one repo serialize fetches that run in parallel today. |
||
|
|
a573c5706b | docs(child-process): note split-relocated allowlist entries | ||
|
|
1d9b5306ac |
chore(child-process): prune stale import allowlist
(cherry picked from commit
|
||
|
|
d5db0bedfc |
fix(main): route browser cookie key commands through runner
(cherry picked from commit
|
||
|
|
d462766cb0 |
refactor(main): split filesystem git remote handlers
(cherry picked from commit
|
||
|
|
08b8f271e6 |
fix(renderer): merge split runtime imports
(cherry picked from commit
|
||
|
|
41015f9393 |
refactor(renderer): split runtime and store modules
(cherry picked from commit
|
||
|
|
51bc2ec343 |
fix(native-chat): keep the attachments on a Claude turn that pasted images (#17801)
* fix(native-chat): keep the attachments on a Claude turn that pasted images
A Claude turn carrying pasted images reached native chat with no images at all —
no thumbnails on mobile, and not even an attachment chip on desktop. Nothing
showed that the message had any.
Both carriers were being dropped:
- Claude records the paths in a companion turn marked `isMeta`, holding one
`[Image: source: <path>]` text block per image. The decoder treats an `isMeta`
user row as injected, filters it down to tool-result blocks, and returns null
when none remain — so the whole row went away.
- The prompt row's own `image` blocks are `{source: {type: 'base64'}}`, which
carry no url or path, so `imageRefBlock` drops them too.
With the companion gone, `isImageSourceUserTurn` could never fire and the fold in
`normalizeImageTranscriptMessages` was unreachable on the Claude path.
Surveying every transcript under `~/.claude/projects`: 238 of 241 image-source
rows are `isMeta`, across every versioned release (2.1.220 through 2.1.237); the
3 that are not carry no version field at all. 38 of those rows hold more than one
content block, which also defeated the single-block rule in
`isImageSourceUserTurn`.
Let image-source text survive the injected-turn filter, and recognize a turn
whose blocks are *all* markers rather than only a lone one. An ordinary injected
turn (a skill preamble, a compact summary) is still dropped, and a turn that
mixes prose with a marker is still not an image-source turn.
Carrying the paths keeps the payload small; decoding the base64 instead would put
hundreds of KB per image on the wire to mobile.
* fix(native-chat): preserve image companion ordering
* fix(native-chat): keep image companions turn-local
---------
Co-authored-by: Merge Sim <sim@local>
|
||
|
|
a9e6fb7eff |
fix(native-chat): stop rendering tool output as the agent's streaming reply (#17782)
* fix(native-chat): stop rendering tool output as the agent's streaming reply A tool result could appear in native chat as a raw, un-collapsed "assistant" bubble that never went away for the rest of the turn — on mobile it showed up as a wall of a source file's contents, prefixed by "Exit code 1". Providers publish a tool's stdout/error as `lastAssistantMessage` so status cards and dashboard rows can preview what the agent just did. Native chat reuses that same field as its live streaming bubble, so the preview rendered as prose. For Claude the preview is *only ever* tool output mid-turn: claude-tool-fields writes real prose exclusively at Stop, so the bubble could never contain an actual streaming reply. It also could not be retired. The bubble hides once a transcript assistant block leads with the streamed text, and tool output never lands in one — so the only remaining exit was the turn ending, which is why a long tool-heavy turn pinned it on screen. Carry provenance instead of changing what the status surfaces show: mark the writes that come from a tool result/error, keep the flag in lockstep with the value it describes through the listener merge, and have both native-chat streaming paths ignore a flagged preview. Status cards, dashboard rows and automation capture are untouched. The wire field is optional, so an older host that never sends it keeps today's behavior rather than silently suppressing previews. * fix(native-chat): preserve tool output provenance through renderer sync * fix(native-chat): retain preview provenance in Claude roster state * test(native-chat): cover restored tool preview provenance --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
a9babde9a3 |
refactor(git): accept a caller-named WSL distro on the metadata path resolver (#17804)
Two small changes to the Git metadata read path. Neither has a user-visible
effect on any platform except for a malformed `.git` gitfile, described below.
1. resolveGitMetadataPath's third parameter becomes an options object
`{ platform?, wslDistro? }`. A caller that knows which distro wrote a pointer
can now say so, where previously only a WSL UNC base path could. The distro
encoded in the base path still outranks the caller's, and translation only
happens when the reading host is win32, so a caller-named distro cannot make
a POSIX host fabricate a Windows path. The UNC-base branch is exempt from
that gate because that spelling only exists on Windows. Main's other
contracts are verbatim: never null for a non-empty pointer, and a drvfs
pointer keeps its drive spelling even when a distro is named. Both production
call sites (repo-git-marker-scan.ts) pass no options, so they are unchanged.
2. The `.git` gitfile marker parse moves into one shared function,
parseGitdirMarkerPayload: `gitdir:` at the start of the file, payload
trimmed, empty payload rejected — git's own read_gitfile_gently rule.
resolve-git-dir.ts and repo-git-marker-scan.ts both call it; the latter had a
near-identical private copy and is behaviorally identical after the swap
(verified across twelve marker spellings; the only divergence, a
whitespace-only payload, already resolved to null one call further down).
Main's `/^gitdir:\s*(.+)\s*$/m` in resolve-git-dir captured trailing padding
into the path and honored a `gitdir:` line anywhere in the file.
Per-platform delta: none on macOS, Linux, native Windows, WSL, SSH, relay, or
folder workspaces. The wslDistro option is inert; this change adds no caller.
For a malformed `.git` gitfile, padding is now stripped (strict improvement), a
whitespace-only payload falls back to `<worktree>/.git`, and a `gitdir:` line
that is not the first line is no longer honored — a narrowing, since main could
return a working gitdir there. All four resolveGitDir consumers already degrade
through a catch, so that case reports no sparse state / conflict operation /
diff stamp rather than failing.
Six other hand-rolled `gitdir:` parsers remain, including the relay's SSH copy;
converging them is its own change.
|
||
|
|
7f63db7d7a |
fix(git): resolve WSL drvfs Git metadata pointers on a Windows host (#17790)
When Orca's runtime is a WSL distro but the repo sits on a Windows drive, git inside the distro writes `/mnt/c/...` into a worktree's `.git` gitfile and its `commondir`, while Orca reads those files back through Win32. `repo-git-marker-scan` returned the pointer verbatim, Windows read it as drive-relative `C:\mnt\c\...`, and the worktree was reported `invalid`. Move that resolver out of `repo-git-marker-scan` into `src/shared/git-metadata-path.ts` and give it exactly one new case: on win32, a drvfs pointer resolved against a base path that is not a WSL UNC path now gets its drive spelling. Every other base/pointer/platform combination is byte-identical to the deleted helper, verified differentially across a base x pointer x platform matrix — macOS, Linux and native Windows are unchanged. `toWindowsWslDrivePath` is factored out of `toWindowsWslPath` so the drvfs matcher has one home; `toWindowsWslPath` itself is unchanged for all inputs, including the line terminators JS `.` excludes (fuzzed 2M inputs, 0 divergences). This changes the marker scan's verdict only. `resolve-git-dir.ts` and the relay's own copy still `path.resolve` the same `/mnt/c/...` pointer in the Win32 namespace, so a worktree that is now accepted still degrades quietly in conflict detection, sparse-checkout detection, the diff stamp and worktree listing. Those parsers are deliberately untouched here; see the PR description. Co-authored-by: Neil <neil@example.com> |
||
|
|
a5796ec8eb |
refactor(runtime): split OrcaRuntimeService and compatibility tests (#17605)
* refactor(runtime): split OrcaRuntimeService into focused modules
* test(runtime): cover admission tiers and strict worktree reconciliation
* fix(runtime): preserve owner and structured session visibility
* fix(runtime): port post-extraction compatibility fixes
* fix(runtime): preserve skill-share cancellation barrier
* test(runtime): update identity inventory after extraction
* fix(runtime): preserve hook transport environment cleanup
* fix(runtime): consolidate idle probe imports
* test(runtime): retire split file process allowlist entry
* fix(runtime): route child process types through shared boundary
* test(runtime): preserve worktree host metadata precedence
* fix(runtime): update extracted test seams
* fix(runtime): gate the split's ts-nocheck set and restore the stop-confirmed contract
Audit follow-ups for the OrcaRuntimeService split:
- Freeze the 171 @ts-nocheck files behind a ratchet so no new file can disable
type checking. The split's linear mixin chain cannot express forward
references yet, so the existing suppressions are grandfathered; the baseline
may only shrink.
- Drop the stray @ts-nocheck at the end of orca-runtime-get-status.ts. It sat
after the first statement, where TypeScript ignores it, so the module was
already checked.
- Restore `retireRejectedPty(ptyId, stopConfirmed: boolean)` as a required
argument. The split widened it to optional and patched the resulting error
with `stopConfirmed === true`; an omitted argument would have silently taken
the unverified-stop path instead of failing to compile.
- Guard that every orca-runtime-tests fragment is imported by the compatibility
entrypoint. The fragments are .spec.ts, which no Vitest include glob matches,
so one left out of the list would silently stop running.
* fix(runtime): restore four behaviors the OrcaRuntimeService split dropped
Audit findings against the refactor's true base (
|
||
|
|
c5d43b8a24 |
Avoid Linear read re-fetches when workspace scope is unchanged (#17529)
* Avoid Linear read re-fetches when workspace scope is unchanged Derive a stable scope signature that captures only the connected state and workspace identity, ignoring volatile metadata like displayName. Use this in dependency tracking so Linear searches don't re-run on status updates that don't affect which issues can be queried. * Expand workspace scope to detect credential and org changes Cache invalidation key now includes credentialRevision and organizationUrlKey for both workspace and viewer, ensuring Linear reads re-fetch when credentials rotate or organizations are renamed — fields that affect what read operations return. * Include activeWorkspaceId in workspace scope signature URL lookup falls back to the active workspace even when all workspaces are selected, so activeWorkspaceId must be part of the scope signature to ensure reads are keyed correctly. |
||
|
|
f116d2ca2a |
test(ci): retry Windows teardown EPERM and restart evaluate misses (#17780)
Restart-survival polls treated a recycled renderer as a hard failure. Wrap those evaluates so "Execution context was destroyed" is a pending miss. Windows package-lane teardowns after a force-kill used rmSync with force:true only, which does not absorb EPERM; put them on the shared maxRetries:8 policy. |
||
|
|
406bd0e378 |
perf(relay): cache process-table descendant indexes (#17646)
* perf(relay): cache process-table descendant indexes * fix(relay): keep the process-table index first-wins and narrow Two defects in the memoized index this PR introduced. - Restore the first-wins duplicate-pid tie-break the relay had as `rows.find()`. A process whose argv contains a newline makes `ps` print a continuation line that the lenient parser can accept as a spurious row duplicating a real pid; that row always FOLLOWS the real one, so last-wins let it capture the pane's foreground. The rule now lives in `buildProcessTableIndex`, so the batched evidence resolver's `byPid.get(rootPid)` root lookup gets the same semantics the subsystem had before indexing. - Build only the two indexes a resolver reads. `byPgid`/`byTpgid` have no readers repo-wide, and delegating to a four-map build made a one-pane relay pay more per 500ms capture than the single `childrenByParent` map it replaced -- a regression in the majority topology, in a PR whose point is relay CPU. Matches the same deletion in #17763 line for line so whichever merges second resolves trivially. |
||
|
|
ad4f068040 |
fix(diff): close large-diff deferral review findings from #17521 (#17758)
* fix(diff): close large-diff deferral review findings from #17521 Deferral keyed "no line counts" off the untracked area, which both prompted ordinary untracked binaries and silently auto-loaded every tracked row when a status pass skipped counting (entry cap hit, numstat failed) — the freeze case the deferral exists for. Decide from the path instead: rows that render as a preview or a binary stub stay automatic, everything Monaco would open as text defers. Also give all three combined-diff virtualizers one shared row estimate, so the PR-review viewers stop estimating a deferred/in-flight large row at 88px while DiffSectionItem renders it at 188px, and drop the dead isLoadOnDemand parameter that estimate covered. * fix(diff): stop deferring cheap uncounted rows the extension list misses The path-only rule relocated friction rather than removing it: every uncounted row deferred unless its extension was in BINARY_FILE_EXTENSIONS, so two classes of tracked row flipped to a "Large diffs are not rendered by default" prompt they had never shown. Tracked binaries outside the list (this repo's own resources/build/icon.icns, plus .tiff/.avif/.psd/.parquet and every extensionless binary) get '-\t-' from `git diff --numstat`, and a submodule whose only change is untracked content inside it gets no numstat row at all while porcelain v2 still reports `1 .M S..U ... sub`. Both are cheap, and both are unreachable from a hardcoded extension list — verified against real git. OR the extension check with two signals already on the entry. A submodule row diffs to a "Subproject commit" line or two whatever it contains, so it is always cheap. And an uncounted row whose siblings in the same pass DID get counts is uncounted for a reason of its own: for a tracked row that reason can only be numstat's binary marker. Untracked rows keep deferring either way, since the scan also skips them past MAX_UNTRACKED_LINE_COUNT_BYTES and their size is exactly what is unknown. No new field crosses git status, the wire, or the section cache; `submodule` and the sibling counts are already there. Fan-out, accepted deliberately: when a pass counts nothing at all — didHitLimit at DEFAULT_GIT_STATUS_LIMIT, or runNumstat returning null — no row has a counted sibling, so the whole combined diff renders as Load prompts. Keeping it. Over 1000 changed entries is precisely the freeze this deferral exists for, and auto-loading that many unbounded Monaco models is the bug, not the mitigation; a numstat failure leaves every size genuinely unknown. Each row still has its own Load diff button, so nothing is unreachable — the only thing missing is a bulk "load all", which would reinstate the freeze on demand. * fix(diff): scope the counted-siblings signal to one counting pass hasCountedSiblings was one boolean over the whole entries array, but that array is not one counting pass. combined-all — the default whenever a branch compare exists — concatenates uncommitted rows with branch-compare rows, and even within the uncommitted set staged and unstaged are separate numstat calls that fail separately. So a single counted branch row vouched for an uncommitted pass that counted nothing (numstat null, or didHitLimit at DEFAULT_GIT_STATUS_LIMIT), and every uncounted row in it auto-loaded into exactly the Monaco freeze the deferral exists to prevent: the guard was off in the default view. Collect the passes that actually counted something, keyed by staging area for status rows and 'compare' for branch/commit rows, and ask that set per row. Untracked rows are unaffected — they never consult the signal. Class 1 of the charter (tracked binaries outside BINARY_FILE_EXTENSIONS) stays open, deliberately. Porcelain v2 reports a modified binary as `1 .M N... 100644` — indistinguishable from text — so only `git diff --numstat`'s `-\t-` knows, and that stdout is parsed on the host (shared/git-uncommitted-line-stats.ts) for both the local and relay status paths. The renderer sees entries, not numstat, so surfacing it per row means a new field on GitStatusEntry and GitBranchChangeEntry that also has to be re-applied in two attachLineStats copies and in the line-stats reuse cache, which persists only {added, removed} and would silently drop it. The one existing field that could carry it — added/removed set to 0 — changes what the host publishes to old clients and mobile, contradicts the documented "undefined for binary files" contract, and collapses the undefined-vs-zero distinction the virtualizer's height estimate reads. So a lone tracked .icns still shows the load prompt; not worth a wire field, and not worth another hardcoded extension. * fix(diff): stop calling an uncounted diff large in the load prompt The deferral prompt had one sentence for two different reasons. A row over MAX_AUTOMATIC_DIFF_CHANGED_LINES really is large. A row with no counts at all — numstat's binary marker, a pass that skipped counting — is deferred because its size is unknown, and "Large diffs are not rendered by default." is simply false for it: a lone tracked resources/build/icon.icns with no counted sibling in its own pass is 4 KB and still says large. Split the copy on the counts the section already carries. No new field on the entry, nothing across the wire, no change to attachLineStats or the line-stats cache — the predicate is renderer-local and mirrors the uncounted branch of shouldLoadCombinedDiffOnDemand, so the two stay in step. |
||
|
|
704167197a |
perf(relay): serve one ps capture per window and pin the batched inventory path (#17763)
Follow-up defect fixes for the batched PTY-inventory evidence path (#17525), now on main. - One memoized `ps` capture serves both the lenient and strict views. The two readers ran byte-identical argv behind separate caches, so a relay serving both forked `ps` twice per 500ms window — the doubling issue #6288 removed. - Drop the `byPgid`/`byTpgid` indexes no resolver reads, plus the zero-caller `parseProcessTableRowsStrict` and `getFreshStrictProcessTableSnapshot`; the batch resolver now reuses the shared index lookup and candidate score instead of private copies. - Restore `getForegroundProcessName`'s ladder contract: the extracted table scan answers null again, so an unconfirmed wrapper fallback publishes the recognized (normalized) name rather than node-pty's raw one. - Pin the SHIPPED `pty.listProcesses` path: one capture and one linear row pass for N panes, and node-pty's own name (never "shell") when the capture cannot disambiguate a `node`/`python` wrapper. - Pin the hidden-pane cadence gate in the production option shape, and move the strict-parser coverage next to the parser it tests. |
||
|
|
26031ca317 |
fix(browser): scroll oversized viewport presets (#17569)
* fix(browser): scroll oversized viewport presets * fix(browser): preserve guest wheel scrolling at viewport edges * fix(browser): keep viewport scroll state synchronized * test: assert partial viewport wheel forwarding |
||
|
|
1a47b9ee85 |
fix(remote): distinguish SSH transport from runtime availability (#17710)
* fix(remote): distinguish transport from runtime availability * fix(remote): preserve transport diagnostics for unavailable runtime * fix(remote): propagate transport diagnostics to host setups * fix(remote): keep unavailable runtimes out of ready setups * fix(remote): preserve unavailable runtime state in settings * fix(remote): preserve reconnecting runtime state * fix(remote): guard stale settings connectivity * fix(remote): preserve diagnostics after main merge * fix(i18n): preserve translations during runtime status merge * fix(remote): refresh settings row health from store * fix(remote): refresh settings row health from store * fix(remote): clear diagnostics generations in tests * fix(settings): refresh runtime availability summary * refactor(runtime): split status slice types * refactor(runtime): reuse status app state type --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
59facfb71e |
Show live tool progress in native chat (#17597)
* Show live tool progress in native chat * fix(native-chat): scope live tool indicator to current turn * fix(native-chat): settle orphaned live tool rows * fix(native-chat): keep live tools running without lifecycle metadata * fix(native-chat): keep working status stable during streaming * fix(native-chat): anchor turn status below prompts * fix(native-chat): preserve turn status and legacy tool activity * fix(native-chat): limit turn status UI to structured Codex --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
8ac1c6e2ac |
perf(git): bound ref and worktree scans (#17655)
* perf(git): bound ref and worktree scans * fix(repo-search): clamp oversized ref limits * fix(worktree): keep strict worktree listing unshared The shared-scan re-export flipped every `listWorktreesStrict` caller from an isolated subprocess to the coalesced scan. `git worktree prune` in the removal recovery path does not bump the scan generation, so a post-prune verification could join a pre-prune scan, see the stale row, and report a successful removal as a stale registration. The same gap defeats the post-archive-hook rechecks that exist to catch an external Git client locking the row. Restore the unshared export and make coalescing opt-in via `listWorktreesSharedStrict`, which existing callers already use deliberately. * fix(git): separate a proven absent ref from a failed probe `show-ref --verify --quiet` exits 1 for a missing ref, but so does `wsl.exe` when its own launch fails, so reading any exit 1 as absence collapsed `unverifiable` into `exited`. A genuine miss prints nothing while a wrapper failure always explains itself, so require empty stderr alongside the exit code; a runner that reports no stderr at all keeps its exit-code contract. That same signal removes a spawn regression: `show-ref` is a direct-git read under WSL, and the runner retried any numeric exit through the user's interactive login shell. The replaced `for-each-ref` exited 0 on a miss, so absence never retried; every absent probe now would. Treat a quiet exit 1 as Git control flow and skip the fallback. Also narrow the hosted-review suffix fallback: the replaced `refs/remotes/*/<base>` could not cross a slash, but `show-ref -- <base>` matches at any depth, so `origin/feature/main` answered a query for `main` and submitted a review against a base the provider rejects. Refresh the real-binary compatibility contract to the shipped excludes, and assert exact probe concurrency rather than an upper bound so a regression to serial probing fails. |
||
|
|
8f15f217a2 |
Preserve user-set workspace names across branch changes (#17448)
* fix(worktrees): preserve user workspace names across branch changes * test(worktrees): cover pinned rename metadata * fix(workspaces): address display-name review edge cases * fix(workspaces): keep automatic names fresh across refreshes * fix(workspaces): preserve legacy CLI labels * fix(workspaces): preserve display-name provenance across hosts * fix(workspaces): honor legacy display-name provenance * fix(workspaces): fence display-name refresh races * fix(workspaces): accept peer renames from provenance-less hosts The old-host preserve fence kept a pinned local label on every refresh, which also suppressed a legitimate rename another client persisted through the same host until app restart. Narrow it to labels the host re-derived itself (branch short name, or path basename when detached); any other changed label in a mode-less response is explicit meta a peer wrote there. Stale prior-label responses stay covered by the downstream staleness fence, in-flight writes by the pending fence. * refactor(workspaces): unify display-name pin derivation Three call sites (renderer optimistic update, local IPC updateMeta handler, remote worktree.set handler) each restated the same formula; a future edit to one would silently skew provenance between paths. |
||
|
|
02a7742406 |
fix(artifacts): raise desktop sharing limit to 5 MiB (#17708)
* fix(artifacts): raise desktop sharing limit to 5 MiB * fix(artifacts): enforce recovery content limit * fix(artifacts): bound recovery request envelopes * fix(artifacts): clarify oversized request error |
||
|
|
872bd51d47 |
fix(native-chat): reland large structured command results (#17720)
* fix(native-chat): preserve large structured command results (#17707) * fix(native-chat): preserve large structured command results * chore: place native chat validation artifacts under docs * chore: drop stale root package config * fix(native-chat): enforce rebuilt lifecycle append slots --------- Co-authored-by: Merge Sim <sim@local> * chore: omit native-chat reland planning docs * fix(native-chat): remove journal store import cycle * fix(native-chat): keep journal factory acyclic --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
9477b5fcbb |
feat(ssh): batch process evidence in PTY inventory (#17525)
* feat(ssh): batch process evidence in PTY inventory * fix(ssh): accept Linux kernel process rows and make no-evidence polling push-driven * fix(ssh): preserve process evidence polling semantics --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
894ed75abb |
Revert "fix(native-chat): preserve large structured command results (#17707)" (#17719)
This reverts commit
|
||
|
|
5fe37729ea |
fix(native-chat): preserve large structured command results (#17707)
* fix(native-chat): preserve large structured command results * chore: place native chat validation artifacts under docs * chore: drop stale root package config * fix(native-chat): enforce rebuilt lifecycle append slots --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
aabcc57366 |
fix(runtime): publish remote control outages to host surfaces (#17531)
* fix(runtime): publish remote control diagnostics to renderer * test(runtime): account for diagnostics bridge listener * fix(i18n): add runtime connection state labels * test(runtime): clean up shared control connection * fix(runtime): fence diagnostics by shared-control capability * fix(runtime): preserve authoritative transport state * fix(runtime): preserve diagnostic overlay lifecycle * fix(runtime): avoid publishing unchanged diagnostics state --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
5ff1aa540e |
fix(codex): re-land WSL direct-home cutover with counsel findings fixed (#16854)
* fix(codex): safely re-land WSL direct homes * fix(codex): finish WSL direct-home cutover * fix(codex): coalesce WSL launch hook installs * perf(codex): avoid duplicate retired WSL session scan * fix(codex): retain canonical WSL retired-home path * fix(codex): fail closed before retiring WSL auth * fix(codex): reopen WSL drain after rollback * fix(codex): preserve WSL source on unknown panes * fix(codex): harden repeated WSL runtime drains * perf(codex): bound pending WSL session scans * fix(codex): recover invalid WSL session watermarks * fix(codex): validate retained WSL scan state * fix(codex): accept durable WSL scan state * test(codex): cover the drain's inode-identity guard against destination replacement Removing the four `target_auth -ef temporary_destination_auth` assertions left all 33 apply-script tests passing, so a regression deleting them would have shipped silently. Reproduced before writing this. A hash check cannot catch the case. The pinned hard link keeps the original inode, so it still hashes correctly after another writer atomically renames a different file over the destination path; only inode identity sees it. Without the guard the script exits 0 and retires the source, leaving the user holding bytes nothing validated. The new case asserts the source survives. The harness is split by responsibility so no file exceeds its max-lines budget: fixtures, the coreutils interference shims, the run types, the apply runner, and the recovery/absent runners. The atomic-rename hook is deliberately separate from the in-place rewrite shim because different guards catch them. * fix(codex): keep the split drain harness inside the child-process boundaries Extracting the harness into non-test modules moved it out of the exemptions the single test file had: three new files import child_process, and two spawned without windowsHide. Adds the three to the import allowlist, and sets windowsHide on the spawns rather than exempting them - the flag is correct for these calls regardless of the ratchet, and they are skipped on win32 anyway. --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
fbe94ceff6 |
fix: close readiness gaps found by merged-change audit (#17159)
* fix(ssh): fence stale kills and retired pane replay * fix(ssh): support cancellable interactive authentication * fix(ssh): await remote catalog before snapshot adoption * fix(pty): contain Windows ConPTY input failures * fix(power): avoid redundant macOS display blocking * perf(editor): narrow markdown override subscriptions * fix(quick-open): close directory handles after reads * refactor(linux): remove unused proc socket scanner * fix(usage): apply flat Sonnet 4.6 pricing * ci: prime Node next native test cache * docs(skills): resolve snapshot cleanup data path * fix(ssh): recover install locks after host reboot * test(ssh): recognize boot-aware install locks * test(ssh): prove previous-boot lock recovery live * test(wire): pin pre-metadata release coverage * fix(terminal): preserve remote tab ownership through recovery races * test(runtime): fence replaced terminal handles in agent guard * fix(ssh): preserve remote snapshot authority across polls * fix(pty): contain late ConPTY output EPIPE * test(pty): register Windows exit watcher before kill * fix: close SSH and tab readiness race gaps * fix(tabs): retain headless order and placeholder titles * fix(build): avoid parallel electron-vite config race * test(windows): avoid MSYS temp path rewriting * test(windows): avoid killing exited PTY * fix(pty): avoid late ConPTY input teardown race * fix(terminal): sync reconnect error ownership after commit * fix(runtime): use canonical worktree identity comparison * test(ssh): assert complete cold-hydration baseline * test(windows): invoke quoted retention fixture via PowerShell * test(windows): read ConPTY grid through mode con * fix(terminal): publish PTY replacements atomically * fix(terminal): infer stale identity on reattach * fix(terminal): fence stale pane PTY callbacks * fix(terminal): fence stale pane binds after rebind * fix(terminal): reject stale pane transport callbacks * fix(terminal): fence mirrored reattach spawn callbacks * fix(terminal): replace stale pane PTYs on remount * fix(ci): size the Windows launcher-compile test budget from measurement `native-smoke (windows-latest)` fails ~4.5% of runs on `preserves a multiline argument through the compiled remote launcher` with "Test timed out in 15000ms" — on unrelated PRs, for reasons that have nothing to do with them. Across 176 sampled attempts it is the only red that job produced, and it hit seven different PRs in two days: #16900, #16904, #16915, #16955 (twice), #16979, #17014, #17085. The test is six process creations: powershell.exe forks csc.exe, then the freshly compiled orca.exe forks node.exe, twice. Hosted Windows runners periodically slow process creation down, and this test amplifies that far harder than anything else in the job. Comparing the 80 attempts where it ran under 3s against the 12 where it ran over 12s, its own median goes 2198ms -> 15917ms (7.2x) while the same file's powershell-only test moves 556 -> 686ms (1.2x), the cmd.exe and Git Bash process tests in the neighbouring file move 1.4x, and the other 35 files put together move 1.5x. Measured across those 176 attempts: 1881ms to 35438ms, p50 4264ms, correlation +0.881 with the job's total Vitest duration. 8 of 176 (4.5%) exceeded the 15s cap; 2 of 176 (1.1%) also exceeded the shared 30s testTimeout, so deleting the override and inheriting the config is not enough on its own. 60s clears all 176 with 1.7x headroom on the worst. This is slow, not hung. Every body here is synchronous spawnSync, so Vitest cannot interrupt one — the timer fires only after the body returns and the reported duration is real elapsed time. That is why a failure reads `× ... 22464ms` under `Test timed out in 15000ms`. The work finished; the stopwatch was short. Seven reruns at one identical head measured 2053 / 4680 / 5551 / 8732 / 13506 / 14868 / 21937ms — the last of those would have been red on code that had not changed. The 15s came from #8897, which raised this test off Vitest's built-in 5s default because the job then ran bare `pnpm vitest run`. #8909 landed 3h27m later and pointed the job at config/vitest.config.ts, which is the real fix for that. The constant stayed behind and has been the binding budget ever since. * fix(terminal): fence stale remount reattach ownership * fix(terminal): reconcile mounted pane identity after replacement * fix(terminal): fence stale reattach fallback ownership * fix(terminal): fence deferred SSH reattach ownership * fix(terminal): fence stale split pane ownership callbacks * fix(terminal): keep stale spawns from consuming startup --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
6bbed15a11 |
fix(worktree): gate agent activation on the live surface census, not renderer state (STA-5701) (#17428)
* fix(worktree): gate agent activation on the live surface census, not renderer state (STA-5701)
* fix(worktree): seed a pane when the surface census cannot prove ownership (STA-5701)
Failing closed must not also fail silent. When the census is unverifiable
the sweep adopts nothing and mints nothing, yet the gate still reported
'adopted' — and both callers suppress their own seeding on any outcome but
'empty', so the workspace ended with zero surfaces. The sweep now reports
whether any live PTY holds a surface and the gate hands the caller its seed
when none does. Also folds equivalent workspace-path spellings in the census
index and in exact-surface binding, so a host row spelled differently is
neither dropped (mint a duplicate) nor unbindable (no pane).
* fix(worktree): name the live PTYs the surface census declined (STA-5701)
The adoption sweep can leave a live PTY without a surface — an unreadable
census, two host surfaces claiming one PTY, or a host-named leaf the
persisted layout does not have. The gate already stops reporting 'adopted'
in that case so the caller seeds a shell, but the decline itself was mute.
- adoptLiveWorkspacePtySurfaces now returns { surfaced, declinedPtyIds }
and the gate warns with the workspace and the PTY ids left unsurfaced.
- Pin the host-named-leaf decline, which had no test either way.
- Pin the superseded-inventory race in terminal.list: a concurrent refresh
makes hostScope.hostIds empty, which is what makes the renderer's
'unverifiable' verdict reachable on a plain local machine.
|
||
|
|
6cc9319d87 |
perf(wsl): warn at project-add when the tree sits on a Windows drive (#17636)
* perf(wsl): warn at project-add when the tree sits on a Windows drive Worktree placement now puts new workspaces inside the distro, but a project whose own tree is on C:\ still pays the 9p/drvfs crossing on every git command it runs — measured at ~20x for a clean `git status` against the same tree on ext4. Nothing in the UI says so, so the project just feels slow. Warn once, right after the add succeeds, naming the distro the project's git actually runs in. The advisory is wrapped so it can never fail the add. Two path shapes cross the boundary and both warn: a Windows drive path under a WSL project runtime, and the UNC spelling of a distro's own drvfs mount (\\wsl.localhost\Ubuntu\mnt\c\...), which crosses it however the runtime is set. A tree already inside the distro, a drive path under Windows-host git, a plain UNC share, and every POSIX/SSH path stay silent. * chore(i18n): register the WSL filesystem boundary advisory keys in en.json |
||
|
|
490a7de5fa | perf(shared): index git history merge parents lazily (#17469) | ||
|
|
4d19b3382c | perf(shared): project automation list in one pass (#17466) | ||
|
|
824dc89e97 | perf(shared): classify folder workspace repos in one pass (#17464) | ||
|
|
1e4c56baa1 | perf(git): classify status line-stat inputs once (#17461) | ||
|
|
3d65466d99 |
perf(agent-hooks): coalesce Codex transcript poll timers
Coalesce per-pane Codex transcript polling onto a shared deadline scheduler while preserving cancellation and stale-callback fencing. |
||
|
|
91cc834584 |
fix(remote): preserve standing host reconnect intent (#17067)
* fix(remote): preserve standing host reconnect intent * chore(lint): merge duplicate imports flagged by the native code-quality audit * fix(remote): fence stale capability runtime identities * fix(remote): release capability evidence on host removal --------- Co-authored-by: Merge Sim <sim@local> |