Commit Graph
11920 Commits
Author SHA1 Message Date
OrcaWinandm4air 47cebbf5d2 ci: use ARM unit runners, overlap web builds, and reuse verifier fixtures (#23376)
* test: reuse isolated mobile bundle fixtures for verifier checks

* ci: run PR unit shards on ARM and overlap independent web builds

* docs: record controlled CI overlap and runner measurements

* test: observe WebRTC packets with the host clock

* ci: isolate Windows installer CIM probe from native test load

* docs: record native probe scheduling validation

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-27 01:06:54 -07:00
NeilandClaude 983250a12e fix: await queued Codex trust preflight completion (#23148)
* fix: await queued Codex trust preflight completion

* fix(agent-trust): bound the trust preflight wait and order the worktree-startup write

The awaited trust write had no cap. The local Codex writer queues on the
per-config.toml lane it shares with hook installs and app-server trust grants,
and the SSH writer chains a resolveHome round trip plus SFTP calls over a link
that may be half-open - so "start agent" could hang with no error. Cap the wait
at a single deadline and continue untrusted, which only loses the ordering
optimisation: the agent then raises its own trust prompt, so giving up fails
closed.

Also await the discarded write in worktree startup, where the PTY spawns Codex
on the next line and the synchronous catch could not see its rejection.

Update the reliability gate: its oracle asserted the absence of a deadline, and
its manifest was left unformatted.

* fix(reliability-gates): record the trust-preflight counts the cited command actually reports

The gate's evidence still described the pre-deadline suite: "32 author tests",
"New11pass", "original4fail/7pass". The four deadline tests this branch adds make
the cited command run 15 tests in `agent-trust-completion.unit.test.ts` and 36
across the four suites, so the manifest asserted counts its own command no longer
produces. Re-ran the command and recorded what it printed.

Re-ran the red/green as well, against the same 15-test suite rather than the 11 it
was first measured on. Pre-await: 5 fail/10 pass. Unbounded-await: 3 fail/12 pass,
where the fourth deadline test — the already-complete write settling on microtasks
— passes unbounded too, so it is a timer control and not a red; saying so beats
counting it as evidence for the cap.

Two claims the commit left stale: only the IPC handler is under test, yet the same
commit also bounds the worktree-startup write, and the cap is per call site while
three other awaited Codex trust writes are still unbounded. Both are now gaps
instead of silence.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-27 00:47:18 -07:00
Neil bc78acc43e fix(editor): detect all bundled Monaco language associations (#23371) 2026-09-27 00:32:55 -07:00
Neil 268f0e9e8d fix(projects): enrich git remote identity for runtime-addressed repo rows (#23300)
A repo row stamped `executionHostId: runtime:<env>` is how a paired client
addresses a project registered in *this* process, so its files are local
(`runtime-repository-registration-controller.ts`, and the helper #23184 added in
`repo-execution-host.ts`). #22421 reclassified those rows as peer-owned and
dropped them from the identity sweep, so `gitRemoteIdentity` never settled on
remote-runtime and paired-web setups: the project stayed pending, fell back to a
host-local `repo:<id>` that never grouped across hosts, and a stale automatic
avatar never repaired.

Reuse `getStoredRepoExecutionHostId` instead of a second host classifier, and
keep skipping only a `runtime:` row that also names a nested SSH target — that
target lives in the peer's dispatch table and the same path here is a different
checkout. The store matches a write against the row's own stamp, so address
`updateRepo` by that stamp rather than the probe host, which is `local` for these
rows and would match no row at all.
2026-09-27 00:31:46 -07:00
NeilandClaude 8da0ca52e7 perf(relay): release rejected first-frame connections [trade-off] (#23011)
* perf(relay): release rejected first-frame connections [trade-off]

* fix(relay): bound the director's rejected and redirected first-frame closes too

The parent PR routed four cell-side first-frame rejections through
closeRelayWebSocket but left two raw socket.close() calls in the same
handler. A real-socket probe shows both still pin a connection unit for
ws's full 30s close timer when the peer ignores the close frame:

- 'invalid invite' is reachable by an unauthenticated peer with a
  well-formed but bogus credential, so the exhaustion the parent PR
  claims to prevent stayed reachable on the director;
- 'connect to assigned cell' is the happy path for every phone's first
  director contact, so it is the highest-volume unbounded close here.

closeWithDrain gets the same treatment; host-session-registry already
closes the identical drain through the helper.

Also records that the bounded close is not a user-facing trade-off: the
close frame is written before the force-close timer can fire and TCP
delivers it ahead of the FIN, so an abandoned peer still reads code and
reason over a graceful close. The new regression asserts that, plus
exactly-once release across concurrent bursts and rejection racing the
peer's own disconnect (the ledger does not clamp at zero, so a double
release would surface as a negative count).

* refactor(relay): drop the unused closeWithDrain helper

`closeWithDrain` has no callers anywhere in the repo, and its `graceMs`
parameter promised a caller-supplied drain window that the body no longer
honours: routing it through `closeRelayWebSocket` force-terminates after 1s
regardless, so a future caller passing `graceMs: 30_000` would have had its
drain silently cut short while the signature still claimed otherwise.

The real drain path is `host-session-registry`, which sends the same
`resolve-director` drain and closes it there. Delete the dead duplicate
rather than bound a helper whose contract says "graceful".

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(relay): call the bounded close's rejection delivery best effort, not guaranteed

The helper claimed the forced terminate costs an abandoned peer nothing it can
observe, and the test presented its fast-peer assertion as proof. Neither holds in
general: `ws` writes the close frame to the socket and `terminate()` destroys that
socket a second later, so under backpressure the frame — and any `relay-moved`
message queued ahead of it — can go unsent even to a peer that never stopped
reading.

Qualifies both comments to describe delivery as best effort and name the
backpressure case. The fast-peer assertion is valid and stays exactly as it was;
only its stated scope narrows, and the test is renamed to say which peer it speaks
for. What the bound actually buys — a stalled peer cannot hold admission — is now
stated on its own rather than resting on a delivery claim.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-27 00:28:52 -07:00
NeilandClaude 8e6c07178e perf: skip store notifications for unchanged project and folder catalogs (#23104)
* perf: skip store notifications for unchanged project and folder catalogs

* perf(store): stop the all-host folder refresh from republishing equal restored owners

The catalog gate landed in this branch suppressed the two catalog publications of
an unchanged all-host folder refresh but left the trailing restored-session-owner
cleanup, which rebuilt `restoredRuntimeHostIdByWorkspaceSessionKey` into a fresh
object on every refresh and so always replaced the store root. Reference-equality
readers (the live dashboard selector, the popout bridge, the tab-create entry gate)
re-ran on that fresh-but-equal identity, so an unchanged all-host refresh still cost
a full publication: 3 -> 1 rather than 3 -> 0.

Reuse `reuseEqualRecordMap` to keep the previous record when the cleanup produces an
equal one, and return the current state when it does. A cleanup that really retires
an owner still publishes.

Also seed the session writer from the current state at creation. `prev === null` is
what bootstraps its first full write, so a writer created when the session gate was
already open owed that write to whatever unrelated store tick arrived next. With
equal catalogs no longer publishing, that incidental wake-up is no longer guaranteed;
evaluating once at creation matches what editor-autosave-controller already does.

* test(store): pin the session writer's creation-time seed

The seed this branch added is the compensating fix for a real regression — it
removed the equal-catalog tick that used to boot the writer's first full write —
but nothing held it in place. Every existing subscriber case creates the writer
with the gate closed and opens it afterwards, so the opening `setState` is itself
the tick that produces that first write; deleting the seed left all five suites
green.

Cover the case the seed exists for: open `workspaceSessionReady` and
`hydrationSucceeded` *before* creating the subscriber, then assert `persist`
fires with a store-tick spy proving nothing woke it. Verified it fails
(`persist` called 0 times) with the seed line removed and is the only failure.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-27 00:28:39 -07:00
NeilandClaude d301658208 fix: exclude canonical SSH roots from local file access (#23196)
* fix: exclude canonical SSH roots from local file access

* fix(filesystem-auth): fail closed on an unplaceable host stamp and an unanchored workspace dir

Two gaps in the canonical-SSH exclusion:

- `getSshTargetIdForExecutionHost` answers null for every host id the shared
  parser rejects (`ssh:`, `ssh:%zz`, `ssh:host|alias`, `SSH:host`, an unknown
  prefix), so those rows read as local and their remote paths were added to the
  local allow-list. Classify by the parsed host instead: remote unless the stamp
  is absent, `local`, or `runtime:`.
- Filtering more repos out makes the `localRepos.length === 0` fallback reachable
  in configurations where it was not, and that branch `resolve`s a repo-relative
  `workspaceDir` against the main-process cwd — an unrelated tree. Only grant it
  when the configured value is absolute on its own path flavor.

Also name each denied fixture case instead of asserting a count, and cover the
local-path-resembling-an-SSH-root and workspace-dir fallback deltas.

* fix(filesystem-auth): share the unplaceable-host denial with worktree-root registration

Main's worktree-root owner rework landed its own local-repo filter built on
`getSshTargetIdForExecutionHost`, which answers null for a stamp the parser
rejects — the same fail-open shape this branch closed on the repo-path side.
Move the hardened predicate into `remote-filesystem-owner.ts` and use it for
both, so `ssh:`, `ssh:%zz`, `ssh:host|alias`, `SSH:host` and `relay:host` can
no longer register a linked worktree root for local access.

Also updates the fixture matrix for main's two-argument
`isRegisteredWorktreePath`, and widens the fixture stamp type past
`Repo['executionHostId']` so the matrix can build the malformed stamps a
persisted catalog actually carries.

* fix(filesystem-auth): decide an unanchored workspaceDir with the host's own path predicate

`settings:set` takes `workspaceDir` unvalidated, and the relative-fallback guard
used `isRuntimePathAbsolute`, which is syntax-based and accepts either flavour.
On POSIX it calls `C:\workspaces` absolute while the imported `node:path.resolve`
reads the same string as a relative name, so the allowed root landed at
`<main-process cwd>/C:\workspaces` and `isPathAllowed` then authorized every
descendant of that unintended local tree. A UNC-shaped value did the same.

`resolveUnanchoredWorkspaceRoot` now pairs the predicate with the resolver in one
place, so whichever `node:path` runs decides absoluteness and produces the root.
Genuine Windows drive and UNC values still grant on Windows, and POSIX absolute
values still grant on POSIX.

Tests inject `path.posix` and `path.win32` to cover all four flavour
combinations without branching on `process.platform`, plus a POSIX-guarded
end-to-end case proving the foreign-flavour string no longer reaches `resolve`.

Also records why folder-workspace authorization stays stricter than
`resolveFolderWorkspaceHost`: there the workspace's own `executionHostId` pin
wins, so a workspace pinned `local` under a group carrying only a legacy
`connectionId` dispatches locally but is denied here. Agreeing would grant a root
the store refuses today, so the fail-closed read stays and a test pins both
answers, including the mirrored row where the two already agree.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-27 00:28:26 -07:00
OrcaWinandm4air 25c3ac400b ci: overlap shell setup, localization extraction, and mobile route preparation (#23368)
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-27 00:28:02 -07:00
Neil fbeaec6b69 Avoid store updates for unchanged SSH repository catalogs (#23034)
* Avoid store updates for unchanged SSH repository catalogs

* test(ssh): pin that the catalog gate fails closed on a Repo field it never enumerated

The gate's safety rests on `structuralValuesEqual` walking own keys generically rather
than on a hand-written per-field comparison, so a `Repo` field added after this landed
is compared without anyone revisiting the gate. Nothing asserted that at the gate
itself — only in the comparator's own unit tests — which is exactly the shape of
regression that would silently stop an update from reaching the sidebar.

Cover the two shapes that matter: an optional scalar key no previous row carried, and
a nested record no previous row carried. `issueSourcePreference: 'auto'` is the sharpest
case, since it is documented as semantically identical to the absent key yet must still
publish under the strict own-key policy.

* test(ssh): wait across decoder turns for the rollback cancel request

The SSH create-operation rollback test polled for `pty.cancelDelivery` with
`await Promise.resolve()`, so it could only observe work that landed in
microtasks. FrameDecoder stops decoding after FRAME_DECODER_MAX_TURN_MS and
finishes the fed bytes from `setImmediate`, and `feed()` will not drain while
that continuation is pending — so on a loaded runner the delivered
`pty.shutdown` response is decoded a macrotask later and the request the test
waits for cannot appear inside its budget. It failed exactly that way on
`tests node 24 8/8`.

Poll across macrotasks instead, matching `waitForRequestCount` in
`ssh-git-provider-test-harness`, and wait for the replacement's source data the
same way. Same assertions; verified by forcing the decoder to yield on every
frame (an ever-advancing `Date.now`), which reproduces the CI error before the
change and passes after it.
2026-09-27 00:27:43 -07:00
AnaandNeil 90bae01db9 fix(editor): highlight Solidity files (#20928)
Map .sol to Monaco's built-in 'sol' language id. The grammar already
ships with monaco-editor; only the extension lookup was missing, so
.sol fell through to plaintext.

Closes #13835

Co-authored-by: Neil <neil@stably.ai>
2026-09-27 00:10:40 -07:00
Neil fc69ec11c6 perf(github): coalesce stronger refreshes after pending requests (#22970)
* perf(github): coalesce stronger refreshes after pending requests

* fix(github): bound the refresh-upgrade wait so a strict caller cannot starve

The upgrade loop retried forever: a caller wanting a stronger refresh waited
for each weaker in-flight request, rechecked, and waited again. A repeating
weaker refresh (the quiet-refresh interval) could therefore pin a forced
noCache caller for the life of the process with no timeout or escape hatch.

Wait out at most one weaker request — enough for peers to share the upgrade —
then issue our own. Call counts are unchanged; progress is now guaranteed.

Retargets the coordination test at that invariant instead of asserting that
strict callers block until the weaker replacement finishes.

* fix(github): let only a dedupe key's current request write its cache

Bounding the upgrade wait fixed the starvation but opened a window the
unbounded loop never had: a stronger request can now run beside a weaker one
for the same key. Nothing fenced the cache writes, so whichever settled last
won. A force-only work-item request does not pass noCache, so gh's own cache
can answer it; settling after the noCache request buried the fresher rows
under a new fetchedAt and isFresh then served them for the rest of the TTL.
Checks rewound run state the same way, and a superseded project request could
stamp its failure over a newer table at the known view key.

Stamp each request with a monotonic id on its inflight entry and recheck
ownership after the provider call, immediately before every cache write — the
work-items entry, both project-view branches, and checksCache plus the PR
status syncPRChecksStatus derives from it. That is the same ownership question
the cleanup guard already asked, so the cleanup now reads the stamp too; the
promise itself cannot be compared from inside its own initializer.

The wait stays bounded and the twenty-one-caller upgrade still collapses to two
provider calls.
2026-09-26 23:57:43 -07:00
c3ff93fd70 fix(editor): highlight Twig templates in files and diffs (#23366)
Select Monaco's bundled Twig language for .twig files, including compound template names. Cover case variants, Windows and UNC paths, and misleading suffixes without changing the recently merged Typst mapping.

Adapted from robbdavis's proposal #22357.

Co-authored-by: Robb Davis <robb@affinitybridge.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 23:55:36 -07:00
OrcaWinandm4air d8e2a694f6 ci: overlap package preparation and security scans; share localization parsing (#23364)
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 23:53:19 -07:00
487bad2596 fix(homebrew): remove deprecated URL verification parameter (#23365)
Remove the deprecated verified URL option from stable and RC Homebrew cask templates. Cross-reviewed against the identical original fix in #18848.

Fixes #18849
Fixes #19580

Co-authored-by: Xavier Xiqués <xavier.xiques@gmail.com>
Co-authored-by: Abdul Munim <423078+munim@users.noreply.github.com>
2026-09-26 23:51:02 -07:00
NeilandClaude 5da18e154f fix: preserve newer hosted review lookup ownership (#23126)
* fix: preserve newer hosted review lookup ownership

* refactor: reuse the shared lookup generation sequence

Drops this coordinator's private counter for the identical allocator added on
the pull-request branch, so the "never reuse a generation id" invariant lives in
one place. The file is byte-identical on both branches, so either may merge
first.

* docs(store): describe the lookup generation sequence by its contract, not its callers

The JSDoc claimed the sequence was "shared by the pull-request and hosted-review
request coordinators", but the pull-request call site arrives in a sibling
change, so on this branch alone the comment named a caller that does not exist.

Describe what the module provides instead: one process-lifetime monotonic
allocator for lookup-ownership stamps, safe to share across every cache because
callers compare stamps for equality only, never order or magnitude. That stays
accurate whether one coordinator draws from it or several, so it needs no edit
when the second call site lands.

Applied identically on the sibling branch so the file stays byte-identical and
the two add/add introductions keep merging without conflict.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-26 23:37:45 -07:00
NeilandClaude 6ccfc1246c fix: preserve newer pull request lookup ownership (#23119)
* fix: preserve newer pull request lookup ownership

* refactor: share one lookup generation sequence between review coordinators

The sibling hosted-review fix needs the same never-reused generation id, so
move the allocator into src/renderer/src/store/lookup-generation-sequence.ts
instead of keeping a second private counter per coordinator.

Also assert in the lifetime test that a stale lookup cannot publish into
prCache and that the live lookup's answer is what lands there.

* docs(store): describe the lookup generation sequence by its contract, not its callers

The JSDoc claimed the sequence was "shared by the pull-request and hosted-review
request coordinators", but the hosted-review call site arrives in a sibling
change, so on this branch alone the comment named a caller that does not exist.

Describe what the module provides instead: one process-lifetime monotonic
allocator for lookup-ownership stamps, safe to share across every cache because
callers compare stamps for equality only, never order or magnitude. That stays
accurate whether one coordinator draws from it or several, so it needs no edit
when the second call site lands.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-26 23:37:28 -07:00
Neil 172083f304 fix: remove PDF export temp files after setup failures (#23130)
* fix: remove PDF export temp files after setup failures

* fix(export): keep PDF temp cleanup to files this export created, and time out a hung load

Two holes in the temp-document cleanup:

- The write was not an exclusive create, so anything already sitting at the
  generated temp path (a symlink planted in the shared temp dir) would be
  written through, and the cleanup would then unlink an entry this export did
  not create. The write now uses `flag: 'wx'`, and the one failure that means
  "this path is not ours" (EEXIST) skips cleanup entirely.
- The 60s timeout only covered render-and-print, started after the load had
  already finished. An export document whose script never yields fires neither
  `did-finish-load` nor `did-fail-load`, so the load await hung forever and the
  hidden window plus its temp file leaked for the life of the app. The timer now
  starts before `loadFile` and races the whole load-render-print sequence.

Tests cover the preserved foreign file, a never-settling load, a load that
resolves without either event, `did-fail-load`, and that one export's cleanup
cannot touch a concurrent export's in-flight temp file.
2026-09-26 23:19:45 -07:00
OrcaWinandm4air ccd1e87287 Overlap independent CI checks with native Actions background steps (#23351)
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 23:09:40 -07:00
Neil 7ac9c8d675 perf: skip macOS DNS probes for unrelated errors (#23121)
* perf: skip macOS DNS probes for unrelated errors

* review(dns-probe-admission): single-source the probe gate and widen resolution-failure coverage

The admission guard duplicated the hint predicate at a second call site, so a gate
that drifted stricter than the hint test would silently drop the DNS diagnostic for
a real lookup failure. Route both through isMacTailscaleDnsHintCandidate, evaluated
once per call, and add a parity test asserting admission never changes the message
the ungated hint decision produces.

Also: widen the predicate to the resolution-failure wordings it missed (EAI_NONAME /
EAI_FAIL / ENODATA / getaddrinfo / "could not resolve" / "name or service not known" /
ERR_NAME_RESOLUTION_FAILED), gate the probe on process.platform === 'darwin' explicitly
rather than relying on the reader's internal check, make the hint idempotent so a
re-wrapped hinted message neither duplicates copy nor re-probes, and rewrite the
cache-window test to assert the resolver state the next relevant error reports instead
of pinning an exact probe count.
2026-09-26 23:07:24 -07:00
Neil 7505e55447 perf: share pending plugin translation startup requests (#23133)
* perf: share pending plugin translation startup requests

* fix(plugins): retry a wedged language-pack startup request instead of joining it

Sharing the pending startup request removed the duplicate IPC call, but the
suppression was permanent: plugins:listLanguagePacks awaits plugin discovery,
so a request that never settles left ensurePluginLanguagePacksLoaded a no-op
for the session. Every later consumer joined a request that would never
finish, where before each one retried — loaded stayed false, pinning the UI on
built-in translations and suppressing the TCC notices that gate on it.

Bound the join to a window instead of a boolean: a request that is merely slow
is still shared, one past the window is treated as wedged and a later consumer
starts its own. Adds the stalled-request test the change was missing.
2026-09-26 23:07:10 -07:00
Neil dc8f623a3a fix: retire stale review lookups after cache invalidation (#23182)
* fix: retire stale review lookups after cache invalidation

* test: pin the fence a retired review lookup answer has to clear

The extraction widened canAdoptDetachedAnswer: a retired owner is no longer in
the in-flight index, so with no replacement reader the scope generation is the
only thing stopping its pre-invalidation "no review" from being adopted as
current — which would short-circuit the lookup for the review Orca just opened.
Cover that interleaving, and restore the invariant comments the extraction
dropped (token identity, expire idempotency, and that a size-cap eviction
forfeits the wall-clock sweep).
2026-09-26 23:06:57 -07:00
Jinjing f5f537ef14 Revert "Support mouse Back/Forward buttons in shortcuts (#23287)" (#23350)
This reverts commit a86fae0889.
2026-09-26 22:58:01 -07:00
NeilandHarshul Rathod bdb897b735 Respect disabled OpenCode variants and refresh WSL settings safely (#23328)
Respect disabled OpenCode variants, preserve explicit config ownership, and refresh WSL guest settings safely across reconnects.

Based on Harshul Rathod proposal #22805.

Co-authored-by: Harshul Rathod <harshulrathod1640@gmail.com>
2026-09-26 22:48:35 -07:00
OrcaWinandm4air 9f5a8a5b8a Reuse mobile recording compilation and refresh desktop CI timings (#23343)
* ci: reuse recording compilation, split families, and refresh shard timings

* Keep recording suite intact after hosted performance comparison

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 22:44:15 -07:00
459410a63b Preserve Hermes YAML configuration during hook installation (#23324)
Preserve supported Hermes YAML values and comments while installing or removing Orca hooks.

Adapted from manthis and Pr1p proposals #22366 and #20632.

Co-authored-by: Maxime AUBURTIN <m@hellomax.io>
Co-authored-by: Chen <zwq19980411@gmail.com>
2026-09-26 22:13:24 -07:00
NeilandKelvin Amoaba 41607a9ddc fix(sidebar): preserve list focus when selecting workspaces (#23320)
Keep keyboard focus in the workspace list during selection changes, and transfer it to the selected terminal on Enter only when focus actually moves.

Based on Kelvin Amoaba proposal #22911.

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-09-26 22:01:24 -07:00
7438bc80f5 fix(orchestration): deliver dispatch mail and replies to the current lead (#23325)
Deliver Dispatch mail and ordinary replies to the current lead Run, preserving original delivery receipts and pre-bind mail draining.

Based on Seongho Bae proposals #22977 and #22979.

Co-authored-by: Seongho Bae <seonghobae@me.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 21:59:39 -07:00
Neil fddf8c8331 feat(editor): highlight Typst files and diffs (#23323)
Highlight Typst files and diffs through the existing lazy TextMate provider.

Based on contributor proposal #22884.
Co-authored-by: Quan Nguyen <qnguyen.dev2@gmail.com>
2026-09-26 21:58:14 -07:00
Neil f41cecc712 fix(search): keep commas inside grouped file filters (#23319)
Keep grouped commas intact when building search filters for ripgrep and Git.

Based on contributor proposal #22915.
Co-authored-by: KAPUIST <thsxornjs12@gmail.com>
2026-09-26 21:58:10 -07:00
Neilanddrakeo338 c3c53f1d24 fix(sidebar): show unread emphasis in compact agent rows (#23327)
Show unread emphasis in compact workspace rows using the existing pane acknowledgment state.

Based on drakeo338’s bounded PR #22870. pinhaum reported #22857 and proposed the broader #22899 variant; the local candidate preserves the existing acknowledgement policy.

Co-authored-by: drakeo338 <paranoyouz@gmail.com>
2026-09-26 21:47:26 -07:00
1c982ff2d9 fix(packaging): exclude root notes from app files (#23326)
Exclude root notes files from packaging while retaining nested runtime notes assets.

Co-authored-by: lurunzi <lurunzi@gmail.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-26 21:45:35 -07:00
NeilandYi-111-a fa3642256f fix(terminal): retain Japanese middle dots and tildes in file links (#23322)
Retain Japanese middle dots and tildes in rooted and explicitly relative terminal file paths; preserve existing path routing and suffix handling.

Co-authored-by: Yi-111-a <47240345+Yi-111-a@users.noreply.github.com>
2026-09-26 21:45:32 -07:00
23e5e77637 fix(grok): show zero usage for an explicit zero quota (#23321)
Infer zero weekly Grok usage for a confirmed explicit zero quota while retaining unreported and monthly precedence safeguards.

Adapted from huiq777’s PR #22303 and the payload analysis from deminit02-hue in issue #20657. The accepted exception is limited to explicit zero cap.

Co-authored-by: Hui <a3239737781@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-26 21:45:28 -07:00
NeilandKAPUIST 2ce47d9bc2 fix(browser): accept dotted hostnames with explicit ports (#23318)
Allow dotted browser domains with explicit numeric ports through the existing URL normalizer.

Co-authored-by: KAPUIST <thsxornjs12@gmail.com>
2026-09-26 21:45:25 -07:00
OrcaWinandm4air b5dec85a4e ci: reuse mobile web route analysis and skip unrelated mobile tests (#23329)
* ci: share mobile route analysis and scope mobile test runs

* ci: cover mobile web runner process dependencies

* test: verify mobile web selectors through the new runner

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 21:37:40 -07:00
Neil 1526d416e2 fix(terminal): recognize a Git Bash launcher by its install layout (#23308)
* fix(terminal): recognize a Git Bash launcher by its install layout

* fix(terminal): require git-bash.exe in the launcher install-layout check

* test(terminal): pin each Git Bash launcher install-layout marker as necessary
2026-09-26 21:35:09 -07:00
NeilandClaude 1f00eaeefd perf(history): coalesce tombstone directory rescans (#23031)
* perf(history): coalesce tombstone directory rescans

* perf(history): reuse one tombstone listing instead of re-reading per completion

Refilling the 64-slot tombstone removal window used to list the whole
`.pending-delete` directory again, synchronously, after every removal that
finished. A backlog of 1,024 tombstones cost 1,026 listings of a directory
that starts 1,024 entries long, all on the main process thread.

Each history root now keeps the names from its last listing and takes the
next one from memory when a slot frees, listing the directory again only
once that list runs out. The listing moved to `fs.promises.readdir`, so it
no longer blocks the main thread.

This replaces the previous coalescing-on-setImmediate approach, which left
the directory being re-listed once per completion batch and needed a
deferred-roots set, an event-loop turn of latency, and an unref'd immediate
whose freed slots could go unfilled at exit. Holding the names removes all
three.

Two behaviours are kept deliberately:

- Freed slots are offered across roots, so a root displaced at the shared
  cap is not stranded until the next startup.
- A listing cannot re-submit a removal that was already under way when it
  started, including one that finished before the listing resolved.

Five real-filesystem samples per version, 1,024 tombstone directories each
holding a meta.json, macOS arm64 / Node 24. Medians: directory listings
1,026 to 6, names enumerated 494,348 to 1,077, blocking main-thread time in
this path 820 to 7 ms, total drain 886 to 75 ms. The 886 ms breaks down as
266 ms of `readdirSync`, ~554 ms of per-entry work over those 494k names
and its garbage, and ~70 ms of actual recursive rm, which is the unchanged
floor the remaining 75 ms consists of. Average concurrent removals return
to 62 of 64 from the 46 a deferred refill left idle.

Adds coverage for a listing that fails mid-drain with later completions
still able to recover, and for both roots draining under one shared cap.
The heavy drain tests now run on real timers so real `fs.promises` and
microtask ordering are exercised.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(history): warn when a tombstone root read fails for a reason other than absence

readTombstoneNames swallowed every readdir error, so EACCES or ENOTDIR on the
initial enumeration left tombstones in place with no diagnostic until a later
completion happened to re-read. An absent root stays silent; it is the norm.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-26 21:22:47 -07:00
NeilandClaude 21170c6e22 perf: avoid repeatedly encoding retained VM recipe output (#23048)
* perf: avoid repeatedly encoding retained VM recipe output

* perf: capture retained VM recipe output as raw bytes in the shared byte buffer

The previous commit added a third byte-retention buffer to the repo. This
replaces it with the one that already existed and removes the remaining
encoding work.

`runRecipeCommand` no longer calls `setEncoding('utf8')` on the child's stdout
and stderr. It keeps the raw `Buffer` chunks and runs one `StringDecoder` per
stream to feed the existing string callbacks, which is exactly how
`setEncoding` is implemented, so callbacks see the same characters at the same
boundaries. With the bytes already in hand the capture encodes nothing: the
4,194,304 bytes the ring still encoded for 4 MiB of output drop to 0, and the
UTF-8 continuation trim collapses from one scan per chunk to a single scan when
the tail is decoded.

Retention is now `GrowingByteBuffer.appendRetainedSuffix`, which had no
production consumer. It gained an O(1) head offset, so `discardPrefix` and
`retainSuffix` mark bytes dead instead of moving the whole tail and `append`
slides or grows only when the head offset runs out of room. Quick Open path
accumulation and the SOCKS handshake buffer get that win too. Without the
offset the per-chunk memmove costs 12.36 ms for 4 MiB; with it, 0.25 ms against
the ring's 0.53 ms and the old per-chunk re-encode's 265.78 ms.

Two behaviour notes. Odd capture limits are clamped once at entry instead of
carrying a per-chunk coercion path no production caller could reach, so an
infinite or NaN cap is now bounded at 1 MiB rather than retaining everything.
And malformed UTF-8 yields a different tail: replacement characters no longer
inflate the byte count, so a malformed tail keeps more of what the recipe
actually wrote.

The encoding-budget assertions no longer spy on `Buffer` itself, where any
unrelated allocation in the same tick could flip them. They count bytes through
the capture's own buffer class and still assert the deterministic oracle: at
most 5 MiB moved for 4 MiB of output, exactly 4 MiB appended, 1 MiB decoded,
and the stored chunks identical to the Buffers the stream delivered.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(vm-recipe): emit Buffers from the doctor stream doubles

Dropping setEncoding('utf8') means stdout and stderr now deliver Buffers, so
the hand-rolled EventEmitter doubles emitting strings threw inside the data
listener — the capture retained nothing and the exit path never settled.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-26 21:22:13 -07:00
Neil 080c4ad62a fix(ssh): name the missing unzip when Bun archive extraction cannot start (#23298)
* fix(ssh): name the missing unzip when Bun archive extraction cannot start

Extracting the downloaded Bun runtime shells out to `unzip` on POSIX hosts,
which a minimal Debian/Ubuntu install does not ship. runProcess rejects a
missing program with a bare `spawn unzip ENOENT`, which the caller's
non-zero-exit branch never sees, so the operator got an errno instead of a
remedy. Translate that one errno into a message naming the tool and the
ORCA_UNZIP_BIN override.

* fix(ssh): reuse the canonical absence predicate for extractor launch failures

isDefinitiveAbsence is the repo's single errno allowlist for "definitively not
there", and it also covers ENOTDIR — which spawn throws synchronously when a
configured ORCA_UNZIP_BIN has a regular file for a parent. That case previously
escaped as a bare `spawn ENOTDIR` naming no path at all.

Also correct the comment: a deleted working directory is not a second source of
these errnos, because runProcess leaves cwd unset and the child inherits the
parent's without resolving it. Verified on macOS, Linux and Windows.
2026-09-26 21:07:59 -07:00
NeilandClaude 1d2c0e5879 perf(mobile): coalesce stalled connection log persistence (#22973)
* perf(mobile): coalesce stalled connection log persistence

* fix(mobile): bound connection-log writes and flush the log before the app suspends

The coalescing pass counted a pending persistence attempt per append and then
burned that whole budget on unblock. With failing storage, 500 appends during a
stall released ~1000 back-to-back `setItem` calls — and slow storage and failing
storage are the same device condition, so the amplification fired in exactly the
scenario the coalescing was for.

The counter is gone. A single `dirtyHosts` flag replaces it: the host's revision
always holds the newest entries, so counting appends bought nothing but writes.
The loop re-reads the revision after each save, so a stall costs the in-flight
snapshot plus one attempt at the newest one, whatever the append count. That also
retires the compound `finally` condition and its unreachable `(… ?? 1) - 1`.

A failed snapshot no longer gets an immediate second `setItem` against a store
that just rejected. It gets one retry after 200 ms, and none at all once a newer
snapshot is queued, because that snapshot already carries the same entries.

`flush()` closes a data-loss gap that predates the coalescing: a write that
failed was only retried by the next append, so when the disconnect was the last
thing to happen the entries explaining it never reached storage. It drains the
in-flight save and makes one more attempt at the newest snapshot, wired to
AppState `background` the way `subscribeConnectionRevivalTriggers` wires resume.

Two revisions tests asserted the retry budget as intended behaviour (6 writes
for 3 appends; 3 for one failure) and now assert one write per snapshot
generation instead. `connection-log-buffer.test.ts` is untouched, including its
requirement that one transient failure self-heals without another append — that
is what the single delayed retry keeps.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(mobile): type the background-flush test mock so the tests ratchet passes

The new test copied `ReturnType<typeof vi.fn>` from
connection-revival-triggers.test.ts, which is grandfathered in the
tests-typecheck baseline for that exact TS2345. The ratchet only shrinks,
so type the mock instead of adding a baseline entry.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-26 20:59:10 -07:00
Neil 067844b646 fix(design-system): replace hand-rolled diff draft-card shadow with shadow-xs (#23307)
The inline AI-note draft card in the diff view used a fourth, hand-rolled
box-shadow tier with raw rgba values instead of the documented shadow-xs
token, so it didn't track theme/token updates like the rest of the UI.
Restated the value under `.dark` too, since it shares selector specificity
with `.orca-diff-comment-popover`'s dark shadow later in the file and would
otherwise lose the cascade to that unrelated rule.
2026-09-26 20:56:41 -07:00
Neil 98cfdc809f fix(kimi): don't crash if config.toml is deleted mid-write (#23293)
* fix(kimi): don't crash if config.toml is deleted mid-write

writeConfigToml checked existsSync(configPath) then called statSync(configPath)
to preserve the file's mode, with no error handling in between. If the file
was deleted in that window (e.g. a concurrent uninstall), statSync threw
ENOENT and the exception escaped install()/getStatus() uncaught.

Now a missing-file stat during that race is treated the same as a missing
file at the check: fall back to the default 0o600 mode and continue the
write. Any other stat error still throws, preserving the existing
mode-read-failure behavior.

* fix(kimi): use isDefinitiveAbsence for the config delete-race check

Reuse the repo's canonical absence check instead of a hand-rolled ENOENT
comparison, per review feedback on #23293. isDefinitiveAbsence also covers
ENOTDIR (an ancestor directory replaced by a file), which the inline check
missed but is equally "the config is definitively not there."
2026-09-26 20:56:37 -07:00
NeilandClaude fed613bab7 perf: avoid rescanning partial notebook output frames (#23138)
* perf: avoid rescanning partial notebook output frames

* perf(notebook): stream bridge frames and skip the unused size accounting

The reader buffered every record of a chunk before delivering the first one, and
asked the framer for byte accounting it can never use. An unbounded line limit
cannot reject, so the per-segment `Buffer.byteLength` and the rejection-prefix
retention were pure overhead for the notebook and Codex readers; both are now
skipped once, behind a hoisted check. Frames are handed to the consumer as each
line completes, so the first output of a chunk paints without waiting for the
last.

The dropped buffer only ever preserved a trailing partial record across a
consumer throw, which cannot help: that throw leaves the stdout 'data' listener
and takes main down with it. Rejections are no longer discarded either — the
bridge keeps fd 1 to itself, so an unreadable line means the frame channel is
damaged and now says so.

Tests move into notebook-kernel.test.ts beside the reader's existing coverage,
and add the never-terminated record and a guard that no record bytes are
measured when no limit applies.

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-26 20:48:19 -07:00
OrcaWinandm4air 1882458f44 ci: scope orcad smoke and parallelize Linux packages (#23314)
* ci: scope orcad smoke and parallelize Linux package formats

* ci: validate packaging when its copy dependency changes

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 20:44:15 -07:00
Neil a2325bbb9b fix(worktrees): clear orphan cleanup on BusyBox WSL distros (#23296)
The WSL "is this path gone?" probe decided a path was missing by matching
GNU coreutils' exact wording, `stat: cannot statx ...`. BusyBox writes
`stat: can't stat ...`, so on an Alpine-style distro a successful orphaned
worktree delete was still reported as a permanent failure, on every retry.

Match only the trailing strerror text, which is POSIX and already pinned to
English by the probe's LC_ALL=C. Permission failures still report failure.
2026-09-26 20:43:45 -07:00
NeilandClaude da95225ba7 perf(push): keep retention sweeps from overlapping without slowing the drain (#23001)
* perf(push): keep retention sweeps from overlapping

* perf(push): drain a saturated retention sweep instead of idling out the tick

The overlap guard on the shared prune timer removed a side effect the sweeper had
been relying on: overlap was the only thing that let a backlog exceed the
50-batch-per-call cap inside one 60-second tick. With the guard, a sweep that
spent its whole budget went idle for the rest of the interval, so a large backlog
drained far slower exactly when retention matters most.

The timer is now a chained setTimeout rather than an interval. `deleteInBatches`
reports whether it exhausted its batch budget, `prune()` returns
`{ deleted, saturated }`, and a saturated sweep is rescheduled immediately. The
connection gate hands a freed slot to the longest waiter, so one serial sweeper
looping back to back still parks a single statement ahead of a worker claim: claim
latency keeps the value the guard bought while the maximum drain rate returns to
what it was before. The loop is self-limiting and stops once the backlog clears.

A sweep that has not settled a full interval after it started now logs
`orca_push_prune_overdue` with its target. Admission waits have no timeout, so a
lost slot release could previously wedge retention permanently and silently.

Chaining makes the overlap guard structural, so there is no flag to scope. The two
single-DELETE sweeps state through `unbatchedSweep` that they have no batch budget
to exhaust, which keeps the immediate-resume path readable as delivery-only.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-26 20:39:57 -07:00
NeilandClaude 375c0279c5 perf: bound wildcard segment work in nested repository scans (#23149)
* perf: bound wildcard segment work in nested repository scans

* perf: bound the ** path walk in nested repository scans, not just one segment

The wildcard-segment fix left the larger blowup in place. A short .gitignore line
made only of `**` segments still costs exponential work in the outer path walk:
`**/**/.../z` at 24 segments and 73 characters, against an eight-segment
candidate, takes about 49 million recursive calls and ~150 ms here — larger than
the 25 ms single-segment case this branch removes. Rules are inherited down the
tree and re-checked for every directory, so that price is per directory, the
runtime's 15s scan timeout fires, and the user silently gets a short repo list.

Two independent bounds, both kept:

- `**` spans zero or more segments, so `**/**` accepts exactly what `**` accepts.
  Parsing now collapses a run of them to one segment, taking the reported shape
  from 49M recursive calls to 26 matcher steps.
- The walk memoizes on (pattern index, candidate index), so no other arrangement
  of `**` can reintroduce the blowup. A rule holding at most one `**` is already
  linear and skips the table, because allocating it costs more than the walk it
  would save on the shapes real ignore files contain.

The budget suite's process-CPU ceiling is replaced by a matcher step counter read
through `readNestedRepoGlobMatchSteps`, so an algorithmic regression fails the
suite rather than passing on a fast machine. Each bound has its own budget case,
and each fails when only the other is applied.

The equivalence oracle now also covers multi-segment and anchored patterns
including `**`, checked against the pre-change per-segment expression walking
uncollapsed segments. Code-unit and metacharacter cases are unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-26 20:39:42 -07:00
OrcaWinandm4air 3eb1adec20 ci: reuse fixture setup and scope localization extraction (#23291)
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 18:46:26 -07:00
OrcaWinandm4air 46907de602 fix(ssh): recover abandoned caches without deleting live dependencies (#23281)
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 18:17:34 -07:00
Neil 5b11834d68 fix(editor): preserve Markdown source across rich edits (#23280)
* fix(editor): preserve Markdown source across rich edits

* perf(editor): remove repeated Markdown suffix scans and block reparses

* fix(markdown): retain case-insensitive editable details parsing
2026-09-26 18:10:21 -07:00