Commit Graph
10572 Commits
Author SHA1 Message Date
ee2faa5a2d perf: index selected ancestor coverage during sidebar drag (#19443)
* perf: index selected ancestor coverage during sidebar drag

* test(sidebar): pin nested and sibling lineage expansion invariants

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:50 -07:00
db5df2f6f6 perf: index selected-host SSH leases by PTY (#19467)
* perf: index selected-host SSH leases by PTY

* perf(ssh): build lease index lazily and cover pty id normalization

* style(ssh): restore repository oxfmt formatting

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:44 -07:00
Neil c3a415487b perf(terminal): avoid quadratic status-marker scans in output bursts (#19373)
* perf(terminal): reuse forward OSC status terminator searches

* test(terminal): pin cached OSC terminator reuse across BEL frames

Document that forward match reuse requires a monotonic search offset and cover a distant ST held across many intervening BEL frames.
2026-09-08 19:41:42 -07:00
d872a5a8b8 perf: avoid repeated pending SOCKS payload copies (#19439)
* perf: avoid repeated pending SOCKS payload copies

* test(browser): cover pipelined SOCKS request-tail ordering in pending buffer

Also document takeBuffer's view-not-copy contract and restore node: import order.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:37 -07:00
d7162ef570 perf: reuse collators when scanning Warp themes (#19441)
* perf: reuse collators when scanning Warp themes

* perf(warp-themes): filter before collating and skip trivial sorts

Warp discovery collated every entry in the user's home or %APPDATA%\\warp before discarding the non-Warp ones; filter first so ICU only sees candidate names (order is unchanged: filtering commutes with a stable total-order sort). Also skip the collator entirely for 0/1-entry directories and single-file dialog picks, and drop the sort that ran only to be thrown away when the preview budget expired.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:15 -07:00
a0ea66a13d perf: reuse collators when sorting discovered skills (#19440)
* perf: reuse collators when sorting discovered skills

* perf(skills): share the discovery-source label sorter

Both native and WSL discovery built the same one-off source collator inline; hoist it next to sortDiscoveredSkills with the same <2 short-circuit, and pin ordering parity against the per-call comparator over a wide collation corpus.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:11 -07:00
400f70b006 perf: index tab membership when rebasing authoritative sessions (#19510)
* perf: index tab membership when rebasing authoritative sessions

* fix(runtime): drop host-removed tab ids from rebased group recents

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:07 -07:00
b68d2c46f6 perf: skip fuzzy ranking when exact file matches fill the window (#19509)
* perf: skip fuzzy ranking when exact file matches fill the window

* test(tab-bar): pin exact-match ordering against the pre-skip rank-then-slice pipeline

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:02 -07:00
b38c1313c4 perf: index discovered skill IDs and names for batch selection (#19507)
* perf: index discovered skill IDs and names for batch selection

* perf(skills): index only the selectors a share request asked for

Indexing every discovered skill made the common one-or-two-selector share slower than the linear scan it replaced (200 skills / 1 ID selector: 0.04us -> 38us). Scoping both indexes to the requested selector set keeps the O(selectors x skills) collapse and beats the unscoped index at every size measured, including 512x512 (5547us old, 176us unscoped, 64us scoped).

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:57 -07:00
de771ae4af perf: concatenate only carried JSONL line prefixes (#19506)
* perf: concatenate only carried JSONL line prefixes

* perf(ai-vault): avoid per-line carry reset and sweep every JSONL chunk boundary

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:51 -07:00
e0cac96772 perf: measure sidebar row geometry once before sorting delete anchors (#19503)
* perf: measure sidebar row geometry once before sorting delete anchors

* test(sidebar): prove the hoisted measure picks the same delete anchor

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:46 -07:00
5f2127ad12 perf: group chat history sequences lazily within result windows (#19501)
* perf: group chat history sequences lazily within result windows

* test(agent-session): prove lazy sequence grouping matches eager at every window edge

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:41 -07:00
8413a5b33c perf: cache same-axis pane subtree weights during equalization (#19500)
* perf: cache same-axis pane subtree weights during equalization

* test(pane-manager): prove cached equalize weights match the uncached walk

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:36 -07:00
6a9c5d8ce3 perf: replay pending chat asks with a FIFO cursor (#19496)
* perf: replay pending chat asks with a FIFO cursor

* perf(native-chat): track the pending-ask FIFO with counters instead of a queue

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:30 -07:00
96216601bc perf: retire exhausted buckets from host-balanced listings (#19493)
* perf: retire exhausted buckets from host-balanced listings

* test(listing): guard the bucket-retirement invariant and starvation fix

Document why in-place compaction keeps bucket[round] defined and preserves
first-appearance host order, and cover cap-exact filling plus small-host
representation so a drifting retirement predicate cannot emit blank rows.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:25 -07:00
814d3525d4 perf: lazily index workspace tabs during terminal binding replay (#19466)
* perf: lazily index workspace tabs during terminal binding replay

* test(persistence): cover same-worktree duplicate tab ids in binding replay

* style(persistence): restore repository oxfmt formatting

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:21 -07:00
2c5cd845a2 perf: append to owned project source lists without repeated copying (#19465)
* perf: append to owned project source lists without repeated copying

* docs(projects): record draft-ownership rule for in-place project merge

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:10 -07:00
96b33d7725 perf: index unified tabs for browser palette entries (#19464)
* perf: index unified tabs for browser palette entries

* test(palette): pin the duplicate-workspace guard the tab index relies on

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:39:58 -07:00
df8a43724f perf: index document history workspace and path identities (#19461)
* perf: index document history workspace and path identities

* docs(browser): note doc-history dedupe must mirror location equality

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:39:42 -07:00
86fe80da25 perf: preserve store state on unchanged document titles (#19459)
* perf: preserve store state on unchanged document titles

* fix(browser): compare every doc-history field before skipping a title refresh

A hand-listed title check would silently swallow any field added to
WorkspaceDocHistoryEntry later. Cover the over-cap trim path too.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:39:27 -07:00
c69d4d4f8b perf: track pane alias singleton or ambiguity without copying buckets (#19479)
* perf: track pane alias singleton or ambiguity without copying buckets

* test(persistence): pin pane-alias ambiguity cardinality parity

Covers 0/1/2/3/4 rows per tab plus a mixed ordering case, so a regression from has() to a truthy check would resurrect an ambiguous tab and fail.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:39:26 -07:00
Brennan BensonandMerge Sim e80fae0c4d Summarize turn file changes and preserve resolved prompt receipts (#19229)
* feat(chat): summarize turn changes and retain resolution receipts

* fix(chat): defer turn diff details and localize resolution times

* fix: complete approval projection test fixture

* Align turn diff disclosure chevron and indent details

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 17:50:51 -07:00
Brennan BensonandMerge Sim d196942220 fix(renderer): align native chat pane layering (#19626)
Co-authored-by: Merge Sim <sim@local>
2026-09-08 16:40:31 -07:00
Brennan BensonandMerge Sim 94a64cf40f fix(browser): load newly opened background tabs (#19633)
* fix(browser): admit newly opened background pages

* fix(browser): scope background page mount admission

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 16:25:14 -07:00
Brennan BensonandMerge Sim 12f2c6b991 Unify native chat and workspace tab command dispatch (#19621)
* fix(tabs): unify native chat command dispatch

* test(tabs): update browser close census

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 14:15:01 -07:00
Brennan BensonandMerge Sim 6b60c23e07 fix(native-chat): reveal message chrome on keyboard focus, not any focus (#19426)
* fix(native-chat): reveal message chrome on keyboard focus, not any focus

`:focus-within` cannot tell a mouse click from a keyboard tab, so clicking any
control inside a message row — a tool run's disclosure, most visibly — parked
focus there and left the row's copy, scroll, and timestamp chrome showing after
the pointer had moved away. The row read as permanently hovered.

`:focus-visible` is the distinction the browser already computes, and it is the
keyboard focus this reveal was written for in #19218. Measured in the running
app: after a real click on a run header, `document.activeElement` is that button
and `:focus-visible` is false, while `:focus-within` is true.

* refactor(native-chat): spell the keyboard-focus reveal with the first-class has variant

Swaps the hand-rolled arbitrary variant `group-[&:has(:focus-visible)]:` for
Tailwind's `group-has-[:focus-visible]:`. Every other `has-` variant in the
renderer is already spelled in the bracket form, including the same
hover-plus-keyboard-focus reveal on the project header actions.

Measured in the running app: both spellings compile to an equivalent selector
and are identical in every state (idle, mouse click on a control inside the
row with the pointer moved away, and each keyboard tab stop).

Also asserts the pointer-events half of both reveals, which governs whether
the copy button is clickable and had no coverage.

* fix(native-chat): preserve message controls on touch devices

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 14:01:45 -07:00
Brennan BensonandMerge Sim d7d21b2c55 Show picker-selected native chat skills as pills (#19616)
* Render picker-selected native chat skills as inline pills

* Use cube icon for native chat skill pills

* Update skill pill label assertion

* Use cube icon for every native chat skill pill

* Use neutral cube icon for native chat skill pills

* Match native chat skill icon to selector

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 14:00:58 -07:00
Brennan BensonandMerge Sim 4f0e3806a9 fix(native-chat): place effort after model picker (#19617)
Co-authored-by: Merge Sim <sim@local>
2026-09-08 12:39:58 -07:00
Jinwoo Hong 8f78c28248 fix(orchestration): fence worker release on mobile keystrokes (#19337)
* fix(orchestration): fence worker release on mobile keystrokes

A settled worker's terminal stayed ownership_state='owned' unless a takeover was
recorded, and the only recorder was orchestration.workerTerminalUserInput, which
only the desktop/web xterm input signal and the native-chat composer call. Mobile
input arrives as terminal.send / stream input frames instead of a report, so a
phone user typing in a settled worker's pane never fenced anything: worker-list
kept recommending release and worker-release closed the PTY under them.

Give the host one definition of "a human typed into this terminal" and route every
lane through it. The mobile input floor claim is that definition and already exists
on both byte lanes: it is taken only for deliberate phone input, never for the
emulator's own query replies, and never for an agent's `orca terminal send`, which
names itself a desktop client and so is indistinguishable from a keystroke at this
layer. Settling that claim after an accepted write now records the takeover through
the same code the RPC reporter uses, throttled to one write per pane per 30s so a
keystroke does not pay for an immediate transaction. The record lands on the runtime
that owns both the terminal and the orchestration database, so SSH-hosted and remote
workers behave exactly like local ones.

No mobile change: mobile already sends client.type (mobile/src/terminal/terminal-send-request.ts:24).

* fix(orchestration): ask the database, do not remember, whether a pane is fenced

The keystroke throttle armed on the attempt rather than on the outcome, so a
zero-row or thrown record poisoned the pane for 30s. A phone keystroke during
the worker-start readiness wait lands before prepareStartingWorkerAuthority
creates the owned resource; a real keystroke seconds later was then suppressed,
the worker settled, and workerRelease closed the terminal under the phone user.
A SQLITE_BUSY on the first write did the same, with no retry.

The cache was the defect, not its arming condition. Its precondition is the set
of owned resources on the pane, which changes underneath it, and any cache keyed
on ownership identity would have to read the database to learn that identity --
which is the whole question. So the input lane now asks: a read using the same
predicate the write uses answers "is anything still fenceable here?" without
taking BEGIN IMMEDIATE, and only then is the write attempted. Ordinary typing
costs a lookup instead of a write lock, a failed write is retried by the next
keystroke, and a takeover writes once per ownership epoch rather than once per
window, because the flip to user_owned removes the pane from the candidate set.
Sharing the predicate keeps the probe from drifting from the writer.

Adds the two escape cases as permanent regressions, drives the mocked send
through the real RuntimeTerminalWriter, and asserts a mobile takeover lifts the
settled-worker resume fence, which no test covered.

* refactor(orchestration): let the database dedupe the takeover, drop the read probe

The probe was meant to keep keystrokes off BEGIN IMMEDIATE, so it had to earn
that with a number. Measured against a real WAL database it costs more than the
write it avoids: at 25 live workers the probe is 0.19ms and the no-op write is
0.10ms, because the probe runs the same candidate selection with each statement
taking its own read snapshot instead of sharing the transaction's. It is a
compensating mechanism with negative value, so it goes, along with the database
method and the predicate extraction it needed.

owned -> user_owned is one-way and scoped to a resource, so the database is
already the dedupe: every deliberate human write attempts the transition, the
second attempt matches no row, and the fence sweep runs only on changed > 0.
Nothing is remembered between keystrokes, so no state can outlive the ownership
it described -- a keystroke before the worker's authority attaches, a write the
database refuses, and a re-dispatch onto the same pane all resolve against the
rows as they are at that instant. An attempt costs about 0.1ms at typical fleet
size and 0.34ms at 100 live workers, on mobile writes only.

Replaces the write-count test, which asserted the old mechanism, with the
invariant: many keystrokes settle into one takeover and one fence sweep. Adds
the re-dispatch case, where a pane's next worker is fenced on its own merits.

* refactor(terminal): name the provenance rule the takeover fence hangs off

The fence rode the mobile input floor claim, with only a comment tying the two
together. The floor is arbitration -- who may write next -- while the fence needs
provenance -- who produced the bytes. They agree today, so anyone reweighing the
floor would have moved the fence without noticing.

isDeliberateHumanInput states the provenance rule on its own terms, and both byte
lanes decide with it when they open a write: the claim carries the verdict beside
the handle, and settlement records the takeover only when a human produced the
bytes. No behavior change -- afterWrite is wired only where the predicate already
answers true -- and the rule is now pinned by its own cases, so a future
arbitration change has to answer this question again rather than inherit it.

* test(orchestration): prove the unary lane classifies a metadata-less phone

A phone build older than client.type is recognised only by its pane's mobile
driver, which the unary lane passes as the provenance evidence. Nothing proved
it did: replacing that argument with false left all 17 tests green while a
shipped phone silently stopped fencing worker release. The new case drives a
clientless send on a mobile-driven pane and fails under that mutation.

The stream lane now passes false outright. Its isMobile is read off the same
client object it carries, so the metadata-less phone cannot reach it, and
passing the flag suggested a legacy path that does not exist there.

Also states what the per-keystroke cost scales with. A pane owning no resource
misses the pane_key index and falls through to a scan of owned resources, so the
figure is tens of microseconds at realistic worker counts rather than a flat
0.1ms, and it grows with rows that are never released.

* fix(terminal): let provenance alone decide the takeover, on every accepted write

A phone older than client.type sends no client metadata, and both stream
initializers derive isMobile from that metadata alone, so such a subscription
reported false and took the stream lane's uninstrumented branch: provenance was
computed and then never consumed. Bytes from a real person landed through both
frame adapters and the resource stayed owned, so workerRelease closed the PTY
under them. The unary lane already fenced that population off the pane's mobile
driver, which is the host's standing reading of clientless input, so the two byte
lanes disagreed at the destructive boundary.

The predicate was still subordinate to floor plumbing: it could only be consulted
where a floor client id existed. Now the accepted-write callback attaches on both
lanes regardless of whether a floor was reserved, and humanInput alone decides
recording; a write holding no claim commits nothing. Arbitration keeps its own
condition around reserveWrite, where it belongs, and the unary lane's duplicate
outer provenance filter is gone. The stream lane reads clientless provenance from
the pane's driver, the same policy the unary lane uses.

The claim holder is now TerminalInputWrite, carrying the verdict beside an
optional floorClaim, so the structure says what the doc said: a write may fence
without holding the floor.

Regressions drive both real frame adapters, clientless direct delivery, and the
paired-web desktop negative. Metadata-only provenance fails 3 on the stream lane
and 1 on the unary lane; gating the callback on a reservation fails the same 3.

* fix(runtime): resolve retained handles before mobile input provenance

A renderer reload clears transient handles while retaining runtime-owned
PTY identities. Legacy mobile provenance saw no leaf, then sendTerminal
restored the same handle and delivered an unfenced key. Normalize through
getLivePtyForHandle at the shared live-leaf resolver entry so classification
and writes agree, preserving existing leaf generation/incarnation checks.

Caller audit:
- terminal-send-method: driver, query-reply authority, lock and floor checks
  now resolve the retained PTY before sending.
- terminal-input-delivery: legacy mobile classification and exact-PTY
  binding now see the same target as the writer; equality checks remain.
- terminal-multiplex-subscribe-resolution: retained PTYs resolve directly
  without a spurious missing-terminal wait.
- terminal-lifecycle-methods resize and terminal-viewport-methods display
  mode, restore-fit and updateViewport retain their original PTY target.
- inspectTerminalProcess: avoids false terminal_gone after reload while
  preserving provider inspection and incarnation fences.
- getLivePaneKeyForTerminalHandle and getOrchestrationDispatchAuthority:
  unaffected because both already call getLivePtyForHandle first.
No wire/schema changes, host fallback, process-death inference, or Git
workspace assumptions; SSH providers keep ownership of execution evidence.

Validation:
- Unmodified round-3 reviewer probe: reproduced 2/2 failures, then 2/2 pass.
- Unmodified round-2 reviewer probes: 13/13 pass.
- Checked-in takeover suites: 24/24 pass. Removing only the resolver call
  fails both new reload cases; source restored afterward.
- RPC orchestration + terminal, aggregate runtime handle registry,
  handle incarnation, mobile tab mount, stale geometry, and reload probe:
  2027 passed, 1 skipped (89 files).
- tc:node and check:code-quality:changed pass; background launch enabled.

* test(rpc): require unconditional terminal afterWrite callbacks

Update exact sendTerminal expectations for the round-2 accepted-write
contract. Preserve beforeWrite expectations, absence of reserveWrite,
byte payloads and call-count checks; require afterWrite to be a function.

Reproduced the requested two-file run: 5 failed, 31 passed. The full RPC
suite exposed the same stale shape in ACK budget/overflow, desktop resize
(including its later retry), and agent-prompt fallback assertions. Update
those too, for 11 assertions across six test files. No production changes.

Validation: ORCA_BACKGROUND_LAUNCH=1 full src/main/runtime/rpc suite:
264 files passed; 2292 tests passed, 1 skipped. Changed-code quality and
staged oxlint/React Doctor/oxfmt checks passed. Ran lint-staged --no-stash
manually to honor checkout safety rather than its default backup hook.

* fix(mobile): report worker takeover outside terminal byte delivery

New phones announce accepted real user input through the existing worker
report RPC, addressed by terminal handle. Share a per-client/per-handle
30-second gate with one bounded retry; report through the same RPC client
as the input. Cover live commits and dictation via their shared sender,
accessory keys, gestures, buffered submit, paste and accepted native chat.
Query replies, attachment heals, triage and diff-review sends do not report.
Phones predating this build do not fence release.

Remove byte provenance and takeover callbacks from host delivery. Restore
both lanes' pre-PR floor-claim plumbing and the original options assertions.
Keep the host recorder uncached with its conditional resume-fence sweep.
No DB schema or stream change; terminal is an optional report address.

Retain the shared resolver recovery independently of takeover: the new
SSH inspection test fails without it during renderer reload. Other callers
still benefit for subscription, resize, viewport and exact-PTY binding;
unary driver/lock checks see the retained PTY. Pane routing and dispatch
authority already recover through getLivePtyForHandle and are unaffected.
Existing leaf generation checks and first-PTY adoption remain unchanged.
No other input-plumbing hunk is retained relative to the PR base.

Replace byte-takeover tests with handle-addressed local/SSH report and
unknown-handle tests, plus real unary/stream writes asserting zero SQL
prepare/exec calls. Mobile send-site integration covers reports, exclusions,
rejected writes and gate counts. Desktop report tests are unchanged.
Register replacement coverage in the settled-worker release manifest.

Validation (all background): host/RPC/runtime 3541 passed, 2 skipped;
mobile session/terminal 2045 passed; node and mobile typechecks, changed
quality, mobile oxlint, reliability manifest and max-lines ratchet passed.
All five requested mutations fail assertions; resolver revert also fails
independent inspection. Staged checks run manually with --no-stash.
Final src diff against PR base: 5 files, +165/-13 (previously +839/-85).

* fix(runtime): allow the takeover report from mobile-scoped tokens

The mobile RPC allow-list gates every phone request before dispatch and the
reporter swallows a refusal, so without this entry every phone shipped
unfenced. Pin it beside the report tests, and pin the once-per-takeover
fence sweep the replaced byte-lane suite used to assert.

* fix(mobile): a no-op takeover report does not arm the gate; Stop reports too

A key during worker startup reports before the resource is owned; caching
that zero-change reply for 30 s suppressed the report that would have fenced
the worker once it attached. Native-chat Stop is deliberate input and now
reports on an accepted Escape.

* fix(mobile): takeover gate ignores the host answer, like desktop

Reopening the gate on a zero-change reply made every accepted key on an
ordinary terminal an RPC plus a host write transaction (round 6: 100 for
100). The startup window it closed is unreachable: the agent has no prompt
to accept input until after its resource row exists. Plain terminals now
pay one report per 30 s window; the native-chat Stop report stays.

Send-site fixture answers the report RPC with a changed count; the draft
test filters to terminal.send calls.

* docs(runtime): say why resolveLiveLeafForHandle re-links before lookup

* chore(i18n): regenerate the runtime-required catalog for the contrast floor strings

* test(orchestration): give the stopping-worker guard fixtures a Run

* test(orchestration): drop fence-sweep assertions retired by the settled-worker policy

* test(orchestration): pin the mid-boot phone takeover that #19608 makes possible

A handle-addressed report during the worker's tui-idle wait now finds the
custody row written at terminal creation, so it flips the pane to user_owned
and worker-release retains it instead of closing it under the user.
2026-09-08 14:48:57 -04:00
Jinwoo Hong 2ba2c90cb6 fix(orchestration): own a worker terminal from creation, not after the boot wait (#19608)
* fix(orchestration): own a worker terminal from creation, not after the boot wait

A worker pane is visible on desktop and phone the moment it is created, but the
worker_terminal_resources row saying orchestration owns it was written only after
the agent TUI went idle (up to 60s). A keystroke into the booting pane found no
owned row, markWorkerTerminalUserOwned returned 0, and the takeover was dropped -
so a later worker-release closed the pane under the user.

Record custody on the branches that create a terminal, right after creation and
before the tui-idle wait. The Dispatch capability still waits for the agent to
come up. An explicit --terminal reuse is untouched: it transfers at authority.

With the row present from creation, the failed-start adoption is dead. What a
failed start still needs is the Dispatch-context pane identity release re-proves
through, which is now copied from the custody row.

* chore(i18n): drop the orphan minimumContrast entries #19544 re-added to the runtime catalog
2026-09-08 14:32:01 -04:00
github-actions[bot] e829bb523a Update README downloads badge 2026-09-08 18:31:49 +00:00
Jinjing 6108ce617c Organize activity menu into filter and view sections (#19547)
* refactor: organize activity menu into sections and change toggle callbac

Restructure the activity thread options menu to use explicit boolean callbacks instead of toggle functions (rename onToggleUnread to onUnreadOnlyChange) and organize options into logical "Filters" and "View" sections. Remove descriptive tooltips for compact mode and unread filter. Rename ActivityScopeFilterMenuSections to ActivityScopeFilterMenuItems and shift layout responsibility to parent component.

* i18n

* fix issues

* i18n

* Hide empty Filters section in activity options menu

- Extract visibility logic into reusable hook `useActivityScopeFilterMenuItemsVisible` to avoid duplication
- Only render Filters label and items when filters are available, preventing empty section in dropdown
- Improves UX by not showing unused menu sections
2026-09-08 10:53:26 -07:00
Neil bba68b1bdd fix(pi): finish the dialog-wait signal on every surface (#19533)
* fix(pi): carry modal waits to mobile and stop losing the dialog close

Follow-ups to #18836, from its readiness review.

- Paint pi's `!` needs-input state marker while a dialog is open, so the
  80ms spinner frame stops repainting a working title over a mid-turn
  wait. Mobile and the CLI read the title, so they saw `working` where
  the desktop already showed `waiting`.
- Keep the assistant reply that lands while a dialog is open. The modal
  guard cleared tool fields and the `message_end` capture with them, so
  a turn ending under a dialog left the preview on the previous message.
- Report `ui_prompt_end` even when `ctx.isIdle()` throws on a runner the
  modal itself invalidated; the lost post stranded the pane on `waiting`.
- Declare the `esbuild` the runtime smoke tool imports.

* fix(pi): hold the needs-input marker until the dialog actually closes

From review of the previous commit.

- Settling under an open dialog no longer retires the marker. stopAnimation
  painted the plain title unconditionally, so agent_settled, a resolved
  agent_end, or an idle auto_compaction_end erased it mid-dialog — and
  because that also cleared the timer, the close then painted the plain
  title again and the wait was lost for good.
- Track the dialog as a boolean, not a depth counter. Pi does its own
  nesting accounting and emits one pair per stack, which is what the status
  extension already assumes; two files disagreeing on that would have let an
  inner close release the outer wait.
- Reset the flag on agent_start in both extensions. A turn cannot begin under
  a dialog holding input focus, so it is the one boundary that can recover a
  close that never arrived instead of pinning the pane forever.
- Leave OMP to its approval events: it reports waits through those already,
  and painting the marker there too would put title and hook in disagreement.

* fix(pi): do not ring the completion bell for a dialog that lost its close

From review of the previous commit.

- Report working, not done, when ui_prompt_end's isIdle() throws. done is
  not cosmetic: it reaches dispatchCompletion and fires the pane's finished
  notification, so a turn that is still running would announce itself. The
  real done still arrives from agent_end/agent_settled.
- Keep the idle-maintenance frame cap accruing while a dialog holds the
  title, so a dialog left open cannot suspend the guard that stops a
  compaction spinner whose end event never came.
- Guard the dialog handlers against a ctx without ui. The source is
  generated and untypechecked, and pi does not document the ctx it passes
  these two events; a TypeError there would surface on every dialog.

* fix(pi): let a turn still complete after a dialog loses its runner

From review of the previous commit.

- Re-arm the completion report when ui_prompt_end's isIdle() throws. The
  fallback posts working, but the finished turn had already reported its
  end, so nothing further would ever fire and an idle pane sat spinning.
- Count dialog depth in both extensions instead of trusting pi to emit one
  pair per stack. The guarantee is undocumented, and if it ever does emit a
  pair per dialog, an inner close would release the wait the outer dialog
  still holds. A counter costs nothing and drops the dependency.

* fix(pi): decide a dialog close from turn state, not from a guess

From review of the previous commit.

- Fall back to agentEndReported when ctx.isIdle is unavailable or throws.
  The previous guess of working stranded the common case — a dialog opened
  at idle — because no later event was coming to correct it, and the
  agentEndReported re-arm it relied on could not fire either. A turn that
  already reported its end is not still running, and that is knowledge this
  process holds without needing ctx at all.
- Only suppress spinner frames once the marker is actually painted. Pi may
  pass a ctx with no ui, and freezing the title on its last working frame
  is the opposite of what the marker is for.
- Gate the titlebar dialog handlers on the OMP runtime too, not just the
  installed kind: a bare-shell OMP launch runs inside a pi-kind pane, and
  the status extension already defers there. Extracted that check so both
  extensions share it rather than carrying two copies.

* fix(pi): treat a pane that never ran a turn as idle, not busy

From review of the previous commit.

- Track turn-in-flight separately from agentEndReported. That flag also
  dedupes the completion post, so it starts false on a pane that has not
  run a turn — which read as still-running and left a dialog opened before
  the first prompt spinning forever.
- Retry the marker paint on each dialog open instead of only the outermost,
  so an outer ctx without ui cannot decide the whole nested stack goes
  unmarked.
- Fall back to the opening ctx when the close carries no ui. Nothing else
  clears the needs-input marker, so the pane would have kept asking for
  attention until the next turn.

* fix(pi): keep a dying dialog ctx from stranding the needs-input marker

The close path paints through the ctx captured at open time, which is the
one a session-switching modal is most likely to have invalidated. Guard
both paint sites so a throw cannot reject the handler and leave the title
on the needs-input marker, and make local turn state the floor for the
status extension's idleness verdict instead of a fallback.

* fix(pi): hold the dialog wait against pi's own title writes and lost closes

Reviewed against real Pi 0.85.1 source rather than inference:

- ctx.ui is a getter that calls assertActive() and throws once a session-
  replacing dialog invalidates the runner, so optional chaining never
  screened it out and the probe sat outside the try. A throw landed after
  the depth decrement but before markerPainted cleared, stranding the
  needs-input marker until the next turn.
- Pi writes the same terminal title from its own writers with no event we
  observe, so the marker is now re-asserted rather than merely not
  overwritten, on a slow timer that outlives the spinner and its cap.
- resetExtensionUI drops an open dialog without resolving its promise, so
  a replaced or reloaded session never emits the matching ui_prompt_end.
  Both extensions now release the wait on session_start and shutdown.

* fix(pi): build the title inside the guard, not as an argument to it

paintTitle caught the setTitle throw but not the two calls one argument to
its left: pi.getSessionName() asserts runner liveness the same way ctx.ui
does, and process.cwd() throws ENOENT once the worktree is unlinked under a
live pane. Four of the six call sites are timer callbacks, where an escape
is an uncaught exception and pi exits(1) through its own handler — so the
cwd route was reachable today. paintTitle now takes a builder and runs it
inside the existing try.

* fix(pi): let only the pane-owning process assert the needs-input marker

The spinner is harmlessly per-process, but the marker is status the pane
reports, and child agents inherit ORCA_PANE_KEY. Gate the two dialog
handlers on a PID claim, mirroring ORCA_PI_STATUS_OWNED in the status hook.
2026-09-08 03:06:54 -07:00
Jinwoo Hong 12f53da542 Remove settled-worker automatic resume and hibernation fences (#19544)
* Remove settled-worker automatic resume and hibernation fences

* test: retirement rollback case follows the no-fence policy

Case 4 seeded and asserted automaticResumeBlockedBy, which this branch
deletes. A rolled-back settled worker is now an ordinary done record that
wake clears as passive evidence, same as any finished agent pane.

* chore(i18n): regenerate the runtime-required catalog for the contrast floor strings

* test(orchestration): give the stopping-worker guard fixtures a Run
2026-09-08 05:14:59 -04:00
github-actions[bot] d058da4786 Update README downloads badge 2026-09-08 08:54:09 +00:00
Neil ea102a9eb8 fix(i18n): drop orphan TerminalPane.minimumContrast entries that broke main static analysis
The 7 auto.components.settings.TerminalPane.minimumContrast.* entries added by #18126 have zero call sites; the shipped component reads settings.contrast.*. Because the runtime-required catalog classifies any key with no literal-default call site as required, the orphans broke 'Verify runtime-required localization catalog' on main and red-lit every PR in the repo.

Deleting them is the root-cause fix: regenerating would instead add dead strings to the boot bundle. On main+delete, --fix regenerates a byte-identical catalog and the CI step exits 0.

Merged with 'test / tests node 24 3/8' red: that failure is an unrelated main break from the #19542/#19551 collision, not from this change.
2026-09-08 01:45:52 -07:00
Jinwoo Hong d346d6f447 fix(orchestration): refuse Task re-open under a live worker; allow stop re-issue on a stranded row (#19551) 2026-09-08 04:03:23 -04:00
Jinwoo Hong 98fdbc4ade fix(orchestration): file federated worker mail under the coordinator Run (#19542) 2026-09-08 04:03:07 -04:00
53852c9ca4 feat(terminal): make the contrast floor user-configurable (#10754) (#18126)
* feat(terminal): make the contrast floor user-configurable (#10754)

The xterm minimumContrastRatio floor was hardcoded (3 on dark backgrounds,
4.5 on light) and applied to every pane with no way out, so TUIs that use
deliberately low contrast were rewritten: Powerline separators drawn in the
neighbouring segment's background became visible seams, and dimmed secondary
text lost its hierarchy.

Adds an optional `terminalMinimumContrastRatio` setting under Settings ->
Terminal -> Rendering. Blank keeps today's automatic, background-luminance
gated floor; 1 disables correction entirely (matching VS Code's documented
`terminal.integrated.minimumContrastRatio` and iTerm2's off-by-default
Minimum Contrast); values are clamped to xterm's 1-21 range.

The floor is resolved in one place, so live panes, the Appearance preview
and the dashboard terminal preview all follow it, and the existing
value-gated write still avoids clearing xterm's contrast cache on no-op
re-applies. The clamp also lives at the persistence boundary that every
writer crosses, so a hand-edited profile or CLI write can never hand xterm
a non-finite option. Mobile mirrors the desktop gate, so the resolved floor
travels with the terminal theme payload as a new optional field; hosts that
omit it leave older and newer clients on the luminance gate.

Fixes #10754.

Co-authored-by: Nyanako <44753291+Nanako0129@users.noreply.github.com>

* fix(terminal): refresh mobile payload fixture and clarify contrast target

* feat(terminal): make contrast controls intent-based with custom tuning

---------

Co-authored-by: Nyanako <44753291+Nanako0129@users.noreply.github.com>
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
2026-09-08 00:39:33 -07:00
Neilandylcn91 2e19342c12 fix(terminal): remove host-retired ghost panes in paired remote splits (#19365)
Adds the missing removal path to the host-authoritative layout reconciler, so a pane the host has retired is unmounted once its PTY has cleared. Fixes #17770.

The removal planner, its retired-set gate, the null-PTY guard, the never-last-pane guard and their unit tests originate from #18387 by @ylcn91. This PR adds the recovery-state dependency that makes the deferred removal actually re-run, an e2e regression spec, and a hook-parity repin.

Co-authored-by: ylcn91 <7249450+ylcn91@users.noreply.github.com>
2026-09-08 00:37:21 -07:00
53233be289 perf: count GitLab diff line prefixes without splitting all lines (#19505)
* perf: count GitLab diff line prefixes without splitting all lines

* test(gitlab): pin diff-count parity for CRLF, lone CR and non-ASCII lines

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 00:10:38 -07:00
Neil 15adbd9d18 fix(agent-hooks): guard every Windows missing-target fallback before it reads stdin (#19415)
A Windows hook whose target file is missing fell back to reading stdin and throwing it away. That read never returns when the caller abandons the pipe, which is what happens outside an Orca pane — one stuck process and a visible console per hook event (#11549).

The rule 'check the Orca env before you own stdin' existed once in cmd syntax and was retyped by hand elsewhere, so the PowerShell and Git Bash launchers never got it. Derive all three dialects from one list of vars and apply them wherever a missing target makes the caller the stdin owner.

- wrapWindowsHookCommand and the runtime-home PowerShell branch guard before ReadToEnd, and emit the fallback answer before the guard so a gate event outside a pane is not answered with silence.
- The runtime-home Git Bash fallback picks its rule by platform: POSIX keeps capture-first (#8110), Windows answers, guards, then drains.
- The Antigravity wrapper disables delayed expansion like its core; with a '!' in the hooks path it was missing the core on every event (#9358/#9941).

Tests drive the wrapper through the production 'cmd /d /c' chain under both delayed-expansion states, and the cross-agent ratchet covers the launchers with an abandoned pipe rather than requiring the unguarded drain.
2026-09-07 23:35:26 -07:00
1a925ed2b9 perf: select highest usage totals without full sorting (#19490)
* perf: select highest usage totals without full sorting

* test(usage): pin first-inserted tie-break contract for highestUsageKey

Document why the strict > and the NaN sort fallback are load-bearing, and
cover the tie/re-set ordering the replaced stable sort guaranteed.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-07 23:28:53 -07:00
6c85e33197 perf: lazily index case-insensitive Windows environment keys (#19483)
* perf: lazily index case-insensitive Windows environment keys

* test(windows): pin env expansion fallback against the per-miss lookup oracle

Adds zero-enumeration, first-case-variant-wins, prototype-chain and 4,000-case
randomized differential coverage, and groups the new cases under their describe.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-07 23:27:37 -07:00
Neil ed881889c4 fix(terminal): fold-safe CAN/SUB and double-ESC handling in partial-escape tail (#19521)
`extractPartialEscapeTail` broke its own fold invariant
(extract(a + b) === extract(extract(a) + b)) in the oscEsc/stringEsc states,
so a PTY read that split there produced a different pending tail than the
same bytes delivered whole — the tail snapshots append after a restore.

Two causes, both in the "ESC did not terminate the string" branch:

- CAN/SUB were routed through `stateAfterEscByte`, which maps them back to
  `esc` instead of aborting to ground. `extractPartialEscapeTail('\x1bPx\x1b\x18X0abc')`
  returned '\x1b\x18X0abc'; the chunk-split fold returned ''.
- A second ESC opened its new sequence at `i - 1` rather than at itself.
  `extractPartialEscapeTail('\x1b] \x1b\x1b^')` returned '\x1b\x1b^' whole but
  '\x1b^' folded. The fold was right — xterm starts the sequence at the second ESC.

The existing fuzz only asserted the fold as `advance(extract(pending), chunk)`,
which is a tautology because every PENDINGS entry is already a tail. Replaced
with a sweep that re-splits the combined stream at every code-unit boundary,
and extended the alphabet (NUL, 0x20 intermediate, CJK) and SEQUENCES with
CAN/SUB and doubled-ESC-inside-string cases. A 1.25M-split fold fuzz over a
VT alphabet goes from 421 failures to 0.
2026-09-07 23:25:46 -07:00
5b111ae607 perf: memoize ancestry when selecting foreground agents (#19502)
* perf: memoize ancestry when selecting foreground agents

* perf(foreground): scope the ancestry memo to its ancestor and process snapshot

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-07 23:24:55 -07:00
Neil ef6ad22431 perf(native-chat): preserve historical tool rows while streaming (#19364)
* perf(native-chat): preserve historical tool rows while streaming

* perf(native-chat): short-circuit identical rows and lock producer immutability

Most folded rows come back as the input object, so compare identity before
scanning fields and blocks. Add a regression test for the invariant the reuse
cache depends on: ordering and folding never rewrite producer-owned messages
or blocks, which reused rows alias.
2026-09-07 23:24:43 -07:00
Neil 28d936e030 perf(native-chat): bound journal reads during paged catch-up (#19360)
* perf(native-chat): bound journal reads during paged catch-up

* perf(native-chat): reduce the journal once per catch-up run, not per page

Bounding the SQL read per page left the JS side still O(total items) per
page: every page re-reduced the whole timeline and rebuilt the live-item
map, and the byte-shrink loop rebuilt it again on each halving.

A catch-up run is a synchronous loop with no await between pages, so the
reduced timeline is loop-invariant. `createAgentSessionCatchUpReader`
holds one snapshot for the run and re-reduces only if the journal cursor
actually moved, and the projection's live-item / alias / submission-byte
indexes memoize on the snapshot arrays the reducer rebuilds on change.

Per catch-up over a 8,000-message backlog: 40 timeline reductions to 1,
reduce+project time 24.3ms to 3.5ms, end-to-end 134.6ms to 111.3ms.
2026-09-07 23:24:25 -07:00
Shahar MorandNeil 668946345a fix(remote): keep terminal tabs syncing after orphan recovery (#19065)
* fix(remote): keep terminal tabs syncing after orphan recovery

* fix(remote): validate recovery snapshots (#19065)

Address CodeRabbit feedback discussion_r3943677920 by validating the complete session-tabs payload before orphan recovery can publish it. Reject malformed rows and metadata as a whole while preserving optional and unknown additive fields.

Add validation and recovery/mirror regressions proving invalid follow-up reads retain the previous inventory and retry successfully.

Validation: 805 tests passed across 49 files; all four changed files pass Oxlint 1.80.0.
Note: pre-existing web typecheck errors in psl/emojibase-data resolution and export-let-function-initializer-ban.test.ts are unchanged from upstream.

* fix(remote): test reachable pending recovery states (#19065)

Address Pullfrog feedback discussion_r3943701696 by removing the retirement guard the host projection cannot reach and validating both affected fixtures through real host finalization.

Cover exact retirement, ready rebinding, pending/no-proof retention, and newer pending rows surviving prior authoritative removal. Preserve the host wire format and retain-on-unverifiable policy.

Validation: 807 tests passed across 50 files; all five changed files pass Oxlint 1.80.0.
Note: pre-existing web typecheck errors in psl/emojibase-data resolution and export-let-function-initializer-ban.test.ts remain unchanged.

* fix(remote): keep recovery reads tolerant of newer hosts (#19065)

Narrow the post-adoption snapshot validator to the fields recovery and the
mirror's coordinate logic actually consume. The previous schema closed every
enum and discriminant on the session-tab channel, so a host that published an
unknown agent name, status state, or tab kind failed the whole parse and
recovery retained forever - the same permanently-invisible-terminal symptom
this PR fixes. Unknown labels now pass through; structural defects in consumed
fields (coordinates, handles, groups, layouts, active selection) still fail
closed, and the three adoption regressions pinning that keep passing.

Replace the cyclic-layout test, which assumed a zod v3 stack overflow that
zod v4 cycle-detects away, with a throwing-accessor case that exercises the
same fail-closed branch.

Thread expectedRuntimeId through refreshWebRuntimeSessionTabsSnapshot so the
fifth recovery call site fences its post-adoption read like the other four.

Takes over stablyai/orca#19065 from its original author.

Co-authored-by: Shahar Mor <shaharmor1@gmail.com>

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-07 23:21:35 -07:00
Jinjing 36d209f515 Verify failure causality in PR checks fix prompt before making changes (#19435)
* Update PR checks fix prompt to verify failure causality before fixing

Revise the prompt to classify failures as caused by this branch, not caused,
or uncertain before making changes. Only proceed autonomously for confirmed
failures; ask the user for guidance on uncertain or unrelated issues to avoid
fixing failures that weren't caused by the branch.

* Update PR checks fix prompt to verify failure causality before fixing

- Emphasize investigation phase by reframing prompt: "Investigate" rather than "Fix"
- Extend untrusted-data warning to all investigation sources (repository files, commit messages, diffs, CI output)
- Add test verifying injection safety: malicious input confined to JSON payloads, never as prompt instructions

* Refactor buildFixChecksPrompt test to focus on field mapping

The wrapper's only responsibility is renaming mobile PR fields onto the
shared prompt builder. Remove assertions about prompt wording, which are
already covered by the builder's own test suite. Simplify the test to
verify the field mapping contract and nothing else.
2026-09-07 22:47:26 -07:00