mirror of
https://github.com/stablyai/orca.git
synced 2026-09-27 00:02:37 +00:00
d17a17684bf97b3fcf2bc393bc4bbd8aaaef11a7
11794
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d17a17684b |
Reduce redundant CI runs, pnpm uploads, and fixture startups (#23145)
* Reduce redundant CI runs, store uploads, and fixture processes * Avoid repeating draft-independent mobile checks on readiness --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
2afb9a385b |
fix(terminal): damp park-verdict churn that is too slow to burst (#20851)
* fix(terminal): damp park-verdict churn that is too slow to burst
The cold-park verdict flip damper only engaged on a burst: 3 flips inside a 1s
window, a threshold derived from React's 50-commit nested-update bail. The
notice limit — 12 flips in 60s — was explicitly breadcrumb-only, on the
reasoning that churn that slow was never near React's bail and damping it would
spend a mounted pane's memory for no crash-safety gain.
The field disagrees about the cost, though not for the reason first recorded.
WHAT THE BUNDLE SHOWS
Bundle Nz4kzIG_NwLd8KObgjJDKA (v1.4.201, win32) carries 52 park-verdict churn
crumbs. They are two unrelated episodes across four launches, not one run:
L3 09-14 00:53 35 crumbs, 1 tab, 46.9 min, 34 window + 1 burst
L4 09-14 17:07 17 crumbs, 4 tabs, 8.9 min, 17 window + 0 burst
L3's 46.9 min counts a lone trailing burst; the window-triggering run itself is
35.2 min, with an 11.7 min quiet gap before that final burst.
Per-flip cadence is elapsedMs / (NOTICE_LIMIT - 1), not / NOTICE_LIMIT: the
window opens ON a flip, so the 12th sits 11 intervals later. L3 runs 3.19s /
4.63s / 5.44s per flip (min/median/max); L4 runs 3.19 / 3.20 / 5.01.
Across 321 field bundles, 24 carry this churn and all 24 burst at least once —
though those 24 are roughly 17 devices, and 23 of them are burst-only, so the
load-bearing fact is narrower than it sounds: this install is the ONLY one that
ever tripped the window rule, and that is what ran undamped.
WHAT IT COSTS, STATED ACCURATELY
Each flip remounts the pane. A remount does NOT reconnect a terminal: parking
deliberately keeps the PTY alive (terminal-parked-tab-watchers runs a pane-less
byte watcher), SSH restores from main's snapshot, and a remote runtime
re-subscribes a stream on a per-environment multiplexer that outlives the pane.
The cost is a remount each time, indefinitely.
The user who filed wrote "The connection closes, reconnects and again and again
and again all the time. Is a cycle." An earlier draft of this change took that
as the mechanism. It is not: the bundle carries no remote-terminal stream-stall
recovery crumbs, no daemon session churn inside either churn window, and one
pane-recovery remount two hours earlier. And the report was filed in L4, 12.3
minutes AFTER that launch's churn had already stopped. The churn is real and
worth damping on its own terms; the causal link to that sentence is not
established, and this change should not be read as proving it.
THE FIX
The notice limit now engages the same unpark pin the burst does, backing off ×2
per consecutive notice window to an 8-minute ceiling, clearing once a full
window closes below the limit.
Measured closed-loop — driving the pin back into the verdict the way
useTerminalParkVerdictPin does — over 47 simulated minutes, counting pane
remounts, which is the user-visible quantity:
per flip transitions before -> after ceiling reached
3193 ms 884 -> 96 (9.2x fewer) 8x
4627 ms 610 -> 96 (6.4x fewer) 8x
4800 ms 588 -> 96 (6.1x fewer) 8x
5000 ms 564 -> 126 (4.5x fewer) 8x
5442 ms 519 -> 156 (3.3x fewer) 8x
These count verdict TRANSITIONS; a mount/unmount cycle is two, so actual pane
mounts are half each figure. The ratios are unaffected. The harness applies the
pin in the same commit cascade as the flip that caused it, which is what the
hook does and what three rounds of review showed an open-loop harness cannot
see.
TRADE-OFFS AND LIMITS, NONE OF THEM HIDDEN
- This does not fix the driver. Whatever keeps re-proposing the park is still
unidentified; the pin masks the rendered verdict and the driver resumes when
each pin lapses. This caps the remount rate of an oscillation; it does not end
one.
- A pinned pane stays mounted and holds its memory, re-armed while churn
persists. One oscillating per-worktree flag flips every tab in that worktree
in the same pass, so all of them can pin at once. The merged verdict includes
the retention-budget force-park, so a churn-pinned pane can stay mounted
through memory reclamation that was trying to free it — and the reclaimer
records success either way, because its only anomaly breadcrumb fires on a
different condition.
The honest number is a duty cycle, not a ceiling. Measured per cadence:
89.7% of wall time pinned at 3193ms/flip, 84.3% at 4627, 83.9% at 4800,
77.8% at 5000, 69.1% at 5442, with a peak contiguous pin of ~480s. Before this
change none of these cadences pinned at all, so blocked reclamation goes from
roughly 0% to 69-90%. Repo-stated cost is ~2.5MB per mounted pane at the 5k
scrollback default and ~19MB at 50k. This is the strongest form of the
original "memory is not free" objection and the change does not answer it.
- Only the notice path backs off; a burst still takes a flat one-window pin.
The corpus supports leaving it: repeat bursts arrive a median 673s apart
(n=29, grouped by bundle/launch/tab; 356s if physical pairs are deduped), and
1 of those 29 inter-arrivals falls in the band that would mean re-firing the
instant a pin lapsed. That margin is thin — the next gap above the band is
75.3s, 0.3s outside it.
- One hard edge, geometric: reaching 12 flips takes 11 intervals, so churn
slower than 60s/11 = 5454.5ms per flip never reaches the limit at all. The
field's slowest observed episode runs 5441.8ms, 12.7ms inside it. Past roughly
4.7s per flip each window only barely makes the limit, so damping engages
later and the win shrinks from 9.2x to 3.3x — but the back-off reaches the
8-minute ceiling at every cadence measured.
- Crumb volume drops by roughly 6x, a loss of
telemetry sensitivity for the very corpus scan that found this. The surviving
'window' crumbs carry sustainedPinCount, which discriminates sustained churn
better than raw volume did; 'burst' crumbs do not carry it. The detection
method changes either way.
REVIEW: TWO ROUNDS, AND BOTH FOUND THE FIX ITSELF
Round 1 found the back-off never left 1x in production. While a tab is pinned
the hook subtracts it from the rendered verdict, so no flip is recorded and
windowStartMs never advances; by the time a pin lapsed, windowStartMs was always
older than a full window, so the quiet branch wiped sustainedPinCount on the
first pass after EVERY pin. Closed-loop, that shipped 3.6x the remounts
intended. The 24 tests then in the file passed either way, because the harness
was open-loop. A second lens found five wrong claims in the commit message,
including a per-flip divisor of 12 where the code uses 11 — which made the
stated safety margin 37x too generous — and a reconnect mechanism the source
contradicts.
Round 2 attacked round 1's fix and found two more:
- The back-off RATCHETED. The flip path cleared it on `flips < noticeLimit`,
and the notice path leaves `flips` at exactly noticeLimit, so a window
reopening after hours of silence did not clear it. Four isolated episodes
six hours apart reached the 8-minute ceiling — the precise behaviour the
code comment claimed was impossible.
- And it was INERT above ~4.7s per flip, because a lapsing pin knocks the next
window out of alignment so it closes at 11 of 12, which that same
`flips < noticeLimit` test read as the churn easing. At the field's slowest
observed cadence round 1's fix changed nothing at all: 519 -> 346 both with
and without it.
Both come from one wrong question. The reset now asks whether the tab has been
genuinely quiet — a full window with no flip and no live pin, tracked on an
explicit lastFlipMs — or whether the closing window held fewer than half the
notice limit. A window closing at 11 of 12 is neither.
Round 3 found the defect inside round 2's fix, and it was one line. Engaging the
pin IS a rendered verdict transition: the hook drops the tab from the parked
set, the effect re-runs on the changed dep, and that flip lands while the pin is
live — overwriting the future deadline with the current time. The back-off then
cleared one window after the pin STARTED rather than one window after it ended.
Round 3 also showed the residual slow-cadence weakness was this bug and not the
geometry the round-2 message blamed, and that reverting to round 2's threshold
still passed all 31 tests. `lastFlipMs` no longer moves backwards off a live
deadline, and a closed-loop test that records a flip inside a live pin now kills
both that clobber and round 2's threshold.
Round 4 found no defect inside round 3's fix and judged it mergeable. It did
find that the eased-flip clause was itself untested — four mutations of it,
including reverting to round 2's threshold, passed every test — and that the
test whose comment claimed to cover it was satisfied by the other disjunct. A
sparse-churn test (three flips per window, indefinitely: eased but never silent)
now separates the two and kills all four.
Also from the review rounds: two separate dead clauses in this function, one
added by each of the previous two rounds, are gone; the burst-interval median in
the shipped comment was one row off (673s, not 732s) and n is 29, not 30. That
median is grouping-fragile — 702.7s by (bundle,tab), 356.1s deduped — but the
facts it supports are not: 1 value in the 59-75s band under every grouping, and
the next gap above it is 75.3s.
Known and not fixed: a tab churning continuously at 6-10 flips per window sits
in the gap between both reset conditions and holds its back-off indefinitely, so
the next notice after it speeds up pins for the ceiling rather than one window.
Stated rather than fixed: because quiet is measured from the pin deadline, a tab
at the 8-minute ceiling needs roughly nine minutes of real silence before the
back-off clears, not one window. The record's doc says so.
* test(terminal): check churn breadcrumb values without assertions
* fix(terminal): let sustained churn pins yield to retention eviction
* test(terminal): preserve an existing burst pin during forced eviction
* test(terminal): verify sustained churn pin expiry and cleanup
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
|
||
|
|
0a5e73e3bb | Leave Open Settings unbound by default (#23136) | ||
|
|
9b30c7f60a |
ci: verify mobile disposal and balance unit-test costs (#23114)
* ci: verify mobile disposal and reduce unit scheduling costs * docs(ci): clarify timing assignment validation --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
4cafa50ec0 |
fix(windows): reuse shared PowerShell literal quoting at every hand-rolled escaper (#23083)
Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
80ff6c4e3e |
fix(startup): read the install-dir package ACL as SDDL so a repaired folder reads clean on Chinese/Japanese/Korean Windows (#22490)
* fix(startup): read the install-dir package ACL as SDDL so a repaired tree reads clean on every Windows language The Windows install-dir permission probe parsed icacls's display output and recognized the well-known package grants by their English names. On Chinese/Japanese/Korean Windows icacls keeps 'NT AUTHORITY' in English but translates the package names, so a tree the repair had just fixed read as poisoned AND reliable: the pre-window repair re-armed every launch, the dialog blamed the install, and the safe-graphics fallback was withheld. Read the DACL with 'icacls <target> /save <tmpfile>' instead, which writes SDDL (SIDs, AC alias) on every locale, and drop the English-name heuristic and its wellKnownNameCheckReliable flag. * fix(startup): reject incomplete saved Windows permission records * fix(windows): reject ACL exports from unsuccessful probes --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
029b5ba1cb |
fix(startup): hold desktop activations until the startup window exists (#22495)
* fix(startup): hold desktop activations until the startup window exists A second-instance, open-url, or open-file activation that landed after app ready but before the startup window was created opened its own main window. The startup open then built a second window, threw on the duplicate 'window:isMaximized' handler, and aborted runtime launch: no runtime RPC, no orca-runtime.json, an orphan hidden window holding the trusted-renderer id, and the visible window refused trusted IPC. The desktop activation gate now starts 'initializing' for every launch mode. The startup window opener releases it once that window exists, so queued activations focus it, and a desktop launch that fails first releases it so later activations can still open a window. Serve mode keeps settling the gate after its RPC is ready. * fix(startup): release desktop activation after failed ready prerequisites --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
df8164f7d6 |
fix(sidebar): "Hide default branch" hides a folder project's root workspace (#22744)
* fix(sidebar): "Hide default branch" hides a folder project's root workspace A folder project's root is its main workspace but has no branch, so the non-empty-branch check never matched it. Folder projects can now hold several workspaces, so the root is the same noise the setting hides for git projects. Decide by repo kind so detached-HEAD and offline-SSH git mains stay visible. * test(sidebar): name the empty-branch git main case for what it tests and refresh the entry-point comment * fix(sidebar): reveal imported folder roots with archived siblings --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
d9bc75752c |
fix(terminal): a remounted new SSH tab keeps the shell its old pane was still spawning (#22578)
* fix(terminal): a pane disposed mid-spawn no longer kills its successor's shell A new terminal tab whose pane remounts while its first pty:spawn is in flight is handed the SAME PTY by main's pane-spawn reservation. The disposed first transport then killed that PTY as an orphan, so the tab closed on pty-exit (focus fell back to tab 1) or stayed bound to a dead shell. Reported on SSH worktrees (scan 22). A transport destroyed mid-spawn now asks the pane surface first and keeps the PTY while the tab exists, the worktree is not being deleted, and any layout still names the leaf (leak-over-kill). A live transport refusing the id via admitPtyId still kills unconditionally (#11003). Adds rate-limited, id-hashed crash breadcrumbs for the next report: terminal_fresh_spawn_retired (killed vs retained), terminal_tab_pty_exit (host kind, ms since spawn, synthetic), terminal_active_tab_auto_move (active-terminal repair, createTab orphan sweep). The two duplicated tab pty-exit handlers now share handleTerminalTabPtyExit, and the FNV id hash used by two crumbs moves to crash-breadcrumb-id-hash.ts. Ports and supersedes #19386 (disposed-spawn-retention and its unit tests, credit to its author); its Docker SSH e2e specs are not included. * fix(terminal): scope spawn retention to its host and keep the PR focused --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
6fc3cdcad6 |
Bundle Bun for headless Orca and profile persistence (#22635)
Bundle a pinned, verified Bun runtime for headless Orca so existing Node launch commands can hand off before opening a profile. Keep desktop execution on Electron. Add the Bun SQLite adapter and terminal backend, bounded shutdown, process inspection and cross-platform artifact qualification. Keep future managed SSH deployment separate from current production launch paths. |
||
|
|
38bcdf76ac |
perf(ci): reduce queue pressure without paid runners (#23053)
* ci: measure complete unit file costs for shard balancing * perf(ci): reduce repeated PR setup and capture complete shard timings * perf(ci): seed reusable main-branch native and typecheck caches * fix(ci): stop superseded unit workflows from resisting cancellation * perf(ci): reuse bundle fixtures and share the baseline Git build * ci: record hosted gains and refresh main hook parity * ci: retain default workers after performance-budget regression * docs: record hosted mobile timing flake --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
49d7ed31c6 |
fix(native-chat): keep chat visible when detaching its pane (#23096)
* fix(native-chat): persist current pane ownership across lifecycle events * fix(native-chat): retain ownership when client chat rendering is disabled * fix(native-chat): preserve chat mode when detaching its pane --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
82412dab8b |
Persist profile state in SQLite with background writes (#22612)
Migrate profile state to SQLite and move writes and backups into a background worker. Acknowledge terminal, SSH and automation changes only after durable saves. Preserve JSON import, recovery, rollback and compatibility exports. Validate migration, worker failures, maintenance, cross-profile moves and terminal lifetime races with unit, integration and end-to-end coverage. |
||
|
|
f0a3610928 |
test(terminal): re-pin the pane hook-order parity past #23049 (#23090)
#23049 added a useRef, a useLayoutEffect and a useEffect to the terminal pane's chat-state, layout-persistence and title-effects hooks and merged with the parity shard red, so main fails 'preserves the recursively flattened render hook order' (211 vs 214). Pin 214 hooks, 7 useMemo, and the new order hash. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
457a84d5a1 |
fix(mobile): label direct paths with the shared Tailscale check (#23039)
directPathForEndpoint hand-rolled Tailscale detection that called any 100.x address a tailnet and missed Tailscale IPv6, so the same endpoint could be labelled differently from the direct connection log. It now uses the shared isTailscaleEndpoint. Also: a stale no-relay comment, a duplicate routing doc line, and the overlay chain settling to Promise<void>. Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
58d1ff3b6a |
Provide the Orca CLI automatically in managed WSL terminals (#22761)
* Provide the Orca CLI automatically in managed WSL terminals * Simplify managed WSL CLI provisioning Never block a shell on CLI availability, keep the shared WSL login-shell builder unchanged, provision from PTY env assembly only, drop the error variable and command probing, and reuse the existing WSLENV helper. * Scope the managed WSL CLI to WSL terminals Provision only for WSL panes and publish the directory through addOrcaWslInteropEnv, so daemon terminals keep inherited WSLENV entries and non-WSL builds never see the variable. Write the bridge with a UTF-8 BOM so Windows PowerShell 5.1 keeps non-ASCII user-data paths, give the dev bridge the dev launcher's app-launch env, and drop the unused skill-setup wiring and runtime capability. * Tighten the managed WSL CLI bridge and setup Launch the bridge child exactly like the registered bridge (no hidden window or output relay; verified through WSL with Node and Electron), give the dev bridge the dev launcher's NODE_OPTIONS stash, clear the guest-only directory before starting Windows processes, collapse setup into one function, warn once, and guard WSL env routing with tests. * Harden managed WSL CLI quoting and inheritance PowerShell also ends single-quoted strings at typographic quotes, so a user-data path such as O'Brien with a curly apostrophe broke the managed bridge. Fix the shared quotePowerShellLiteral and reuse it. Drop an inherited ORCA_WSL_CLI_DIR on the daemon path, remove the unreachable PATH dedupe, and cover failed setup with a stale caller value. * Cover the managed WSL CLI in zsh and on POSIX CI Add a live zsh case that reaches a real prompt, a POSIX test that runs the PATH restore snippet in bash and zsh under set -u, and a null result for unwritable user data. Say what a failed write actually costs, and document per-spawn write logging and older-daemon behaviour. * Keep system bashrc out of the PATH restore test CI runners make bash -c read /etc/bash.bashrc, which fails under set -u. --------- Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
85ac14e9c2 |
fix(codex): retain runtime MCP entries without losing revocation (#22426)
* Retain runtime-only MCP entries Adapted from the investigation and proposal by @mmarabel. Co-authored-by: mmarabel <mmarabel@users.noreply.github.com> * fix(codex): respect inline and dotted canonical MCP ownership * fix(codex): retain canonical MCP removal across upgrades * fix(types): include MCP ownership in CLI project * Keep unrelated main test formatting unchanged --------- Co-authored-by: mmarabel <mmarabel@users.noreply.github.com> |
||
|
|
aed59a797a |
chore(i18n): translate 113 new keys to es/fr/ja/ko/zh (#23067)
* Add translations for chat resume, Git toolchain, and notebook support
* Fix translation terminology in French and Korean locales
- Standardize Korean terminology from "restart" to "resume" for chat
resume functionality
- Clarify French error message for conflicting Orca windows/terminals
- Fix Korean context translation (문맥 → 컨텍스트)
* fix jupyter notebook translation
* chore(i18n): translate 113 new keys to es/fr/ja/ko/zh
Delta since last scan (
|
||
|
|
e13631ee53 |
Prioritize workspace opening over replacement checkout preparation (#23013)
* Prioritize workspace opening over replacement checkout preparation * Preserve Git hook semantics and exercise preparation edge cases |
||
|
|
f6eab381ce |
fix(types): describe command environments independently of Expo globals (#23073)
Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
067975bfd1 |
fix(native-chat): every lease latch has a way to die (#22820)
* fix(native-chat): every lease latch has a way to die A failed exit settlement no longer leaves the lease in recovery: the release writes no stage and keeps the exit in its death evidence, and whatever the dead generation left running is settled from that evidence at the next acquire or read restore. The settlement retry flag, its disposition and every branch that read it are gone. A reservation that recorded no process is released at startup and after a failed start, the never-written conflicted status and the processless proof are deleted, recovery resolution always concludes, and Codex records its child's identity at spawn, before the handshake. * test(native-chat): a re-create needs a release proven by death evidence * test(codex): the child's pid is reported before the handshake * test(native-chat): type the crash and exit fixtures without casts * fix(native-chat): wait out a terminal owner an older build recorded, in recovery rather than manual recovery * test(native-chat): a chat mid-turn at quit reopens idle, and an older build reads an unproven release * test(native-chat): explain the baseline store cast * fix(native-chat): a terminal owner's refusal names the process instead of recursing Opening a chat whose terminal owner an older build recorded threw a stack overflow instead of the refusal that names the process to quit. * fix(native-chat): wait out a terminal owner recovery cannot verify instead of releasing it A terminal agent an older build recorded keeps its PTY across an Orca restart, so a probe that cannot answer (a start-time read that fails on a loaded host) is not evidence its transport is gone. Releasing it let a native child resume the same conversation beside the live terminal agent. Only proof of its exit now ends the claim. * ci(cross-version): run the unproven-release downgrade test The sharded unit job excludes tests/e2e/cross-version-wire, and the cross-version job runs an explicit list that did not name the new test, so it never ran in CI. A change to the record validator now also starts the job. * refactor(native-chat): map the retired manual-recovery stage to recovering at decode Nothing in this build writes manual-recovery, and restart reconciliation already rewrites it. Mapping it where the other retired handoff stages are mapped removes it from the in-memory lease type and deletes the branches that could only see it: the acquisition refusal, the renewer skip, the unproven-release stage check, and the handoff-status 'manual recovery is required' answer. Older builds accept recovering, so a record written back still loads after a downgrade. * docs(native-chat): say what happens to a live child an ownerless reservation leaves The reaper runs once at store open, while the unreconciled lease still claims the child's token, so it does not stop that child on this launch. The comment claimed it did. * test(native-chat): name the each-case label for its role * fix(native-chat): continue a create retried after recovery released its reservation The client retries a create it never heard back from under the same operation id. Recovery had released that create's reservation, so the retry was refused agent_session_ownership_unknown while its row was pending, and agent_session_operation_expired once the row aged out, and the chat never started. A retry whose lease nothing holds now continues as a fresh reservation at the next fence, which also stops the old reservation's spawn from committing. * test(native-chat): name the refusal a replayed create used to get * fix(native-chat): one quit-the-terminal-agent message for a chat a terminal agent holds A chat held by a terminal agent an older build recorded frees only when that agent exits. Sending said to reopen the chat and opening it said two runtimes claimed it; both now say the chat is open in a terminal agent, name its process, and say to quit it. Error codes are unchanged. * ci: run PR checks on the rebased head * fix(native-chat): name a terminal owner's process only when its start time can tell it from a reused pid * test(native-chat): relaunch from the dying host's durable state, so its still-pending attach cannot race the new host |
||
|
|
d06b43e634 |
fix(tasks): read a malformed saved Linear team selection as sticky-all instead of crashing the page (#22279)
* fix(tasks): read a malformed saved Linear team selection as sticky-all instead of crashing the page A persisted defaultLinearTeamSelection that is not a string array (a string reached 1.4.207, report 0a2b6e7f) threw '(t ?? []).filter is not a function' inside a commit-phase effect and tripped the page.tasks error boundary. The value is now normalized where the page reads it and where a host projects it to paired clients; anything but a string array means sticky-all. * fix(mobile): read a malformed saved Linear team selection as sticky-all A host that predates the desktop fix projects its raw store value, so the mobile Linear list must tolerate the same string shape. Also trims the desktop helper's comments to the why. * fix(tasks): validate projected Linear team IDs and refresh parity contract --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
27ca6a229a |
test(mobile): repin RPC goldens to main after #22956's squash (#23046)
#22956 re-recorded with baseline = its own branch commit
|
||
|
|
15baf86660 |
fix(agents): honor environment prefixes in generation commands (#22427)
* fix(agents): preserve environment prefixes in generation commands Adapted from the proposal by @carlosbaraza. Co-authored-by: carlosbaraza <carlosbaraza@users.noreply.github.com> * test(agents): respect Windows environment key normalization --------- Co-authored-by: carlosbaraza <carlosbaraza@users.noreply.github.com> |
||
|
|
d929bd8f56 |
Keep large Windows file identities distinct (#22424)
Adapted from the investigation and proposal by @Sungjae-Heo. Co-authored-by: Sungjae-Heo <Sungjae-Heo@users.noreply.github.com> |
||
|
|
094fbef08c |
fix(toast): keep folder errors above standard modal backdrops (#22423)
* fix: make folder errors visible above dialogs Co-authored-by: midego <61051030+midego1@users.noreply.github.com> * test(toast): explicitly isolate background app launches --------- Co-authored-by: midego <61051030+midego1@users.noreply.github.com> |
||
|
|
f851d18e91 |
Preserve Kimi config permissions (#22422)
Adapted from the investigation and proposal by @Pr1p. Co-authored-by: Pr1p <Pr1p@users.noreply.github.com> |
||
|
|
18d00ccfe6 |
fix(projects): refresh stale automatic GitHub icons during enrichment (#22421)
* fix: repair a bounded stale project-icon case Based on the report and proposal by @mmarabel. Co-authored-by: mmarabel <mmarabel@users.noreply.github.com> * fix(projects): preserve peer-owned remote metadata * fix(projects): keep enrichment within the owning host --------- Co-authored-by: mmarabel <mmarabel@users.noreply.github.com> |
||
|
|
7d4413b3d7 |
fix(pdf): keep the search counter in sync with selected matches (#22420)
* fix: update the PDF search counter Co-authored-by: BM Cho <bm1016bm@gmail.com> Co-authored-by: makoto-developer <72484465+makoto-developer@users.noreply.github.com> * test(pdf): respect explicitly headful launch mode * test(pdf): wait for rendered folder search highlights * test(pdf): wait for rendered text before initial search --------- Co-authored-by: BM Cho <bm1016bm@gmail.com> Co-authored-by: makoto-developer <72484465+makoto-developer@users.noreply.github.com> |
||
|
|
9d5591b791 |
fix(jira): bypass collection caches on explicit refresh (#22419)
* fix: refresh Jira lists immediately Based on the report and proposal by @chaiyapod. Co-authored-by: chaiyapod <chaiyapod@users.noreply.github.com> * fix(jira): ignore superseded collection auth failures --------- Co-authored-by: chaiyapod <chaiyapod@users.noreply.github.com> |
||
|
|
7889a25b7f |
fix(cli): preserve the WSL distro when adding managed accounts (#22418)
Keep the caller distro across the Windows bridge, including drive-mounted working directories, and pass it through the existing account imports. Retain the contribution from PR #17093 and cover empty/space-containing bridge arguments, platform boundaries, and ambient environment conflicts. Co-authored-by: Joao Nicola <jgrnicola@gmail.com> |
||
|
|
14087c8e32 |
Accept repeated leading BOMs in agent hooks (#22414)
Adapted from the investigation and proposal by @bbingz. Co-authored-by: bbingz <bbingz@users.noreply.github.com> |
||
|
|
2ed6505a41 |
fix(native-chat): preserve current pane ownership through toggles and restore (#23049)
* fix(native-chat): persist current pane ownership across lifecycle events * fix(native-chat): retain ownership when client chat rendering is disabled --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
d1eb64ea41 |
fix(editor): use the bundled ABAP grammar (#22417)
Co-authored-by: Jonghwa Hong <zzzonghwa@gmail.com> |
||
|
|
ce2f75e172 |
refactor(mobile): the live input's composing range comes from a platform seam pair (#23037)
#22958 made the page on Android report no composing range with a user-agent check inside the shared live-input hook. Host facts live in a `src/platform` pair, so the check moves to `live-input-composing-range.web.ts`; the native file passes the event's range through. Behaviour is unchanged. The hook test mocks the seam; the user-agent cases move to the seam's own test. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
77bc0d77d8 |
fix(editor): highlight scoped dotenv filenames on desktop and mobile (#22416)
Co-authored-by: willydallas <willy.dallas@pm.me> Co-authored-by: Wooseong Kim <innocarpe@gmail.com> |
||
|
|
02a5594434 |
fix(mobile): give each host field one writer so relay routing can't revert edits (#22956)
* fix(mobile): give each host field one writer so relay routing can't revert edits Relay learners (director re-resolution, credential rotation, direct-to-relay upgrade) saved the whole HostProfile snapshot their connection opened with, so a late relay write reverted an Edit Host endpoint and rewrote the device token. The relay overlay also stored a copy of the paired address, which the direct probe kept dialling after an edit. Pairing is now the only full-profile writer (savePairedHost, fenced by a census). Learners call setRelayRouting(hostId, relay), which never touches the row or keychain, refuses a removed host, and skips unchanged routing. The overlay is relay-only in memory; one serializer writes the v2 shape older builds parse, without the direct entry. Saving a new endpoint rebuilds even a Relay-active client from the saved row. Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> Co-authored-by: Neil <neil@stably.ai> * test(mobile): re-record RPC goldens for relay-only host routing Baseline moves to the product commit. adapterSha256 moves for the pairing and relay-credential adapters, which now read relay.relayHostId and inject saveRelayRouting. Only the four direct-upgrade bodies change: the settled host no longer carries the overlay's endpoints copy (direct-primary, relay-primary) or the duplicate relayHostId; relay and every effect are unchanged. Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> Co-authored-by: Neil <neil@stably.ai> * refactor(mobile): relay learners own only relay routing Follow-up to the field-ownership split. The supervisor keeps its host read-only and holds the relay in a small owner that persists moves through setRelayRouting; the direct upgrade returns { relay, bundle } and the lifecycle composes the profile once. With one direct endpoint left, the probe takes a bound openDirect and the lifecycle computes the direct path once. One name per writer: setRelayRouting and savePairedHost are also the dependency keys, and the removed-host error is RelayRoutingHostRemovedError. The census now counts real value imports of savePairedHost, not mentions. Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> Co-authored-by: Neil <neil@stably.ai> * test(mobile): re-record RPC goldens for the { relay, bundle } upgrade result Baseline moves to the refactor commit, and adapterSha256 moves for the pairing and relay-credential adapters (dependency keys renamed to savePairedHost and setRelayRouting). Only the four direct-upgrade bodies change: the settled upgrade result is now { relay, bundle } instead of { host, bundle }, with identical relay, bundle, state and effects. Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> Co-authored-by: Neil <neil@stably.ai> * refactor(mobile): refresh edited hosts and let the establisher own relay Edit Host now reuses refreshHostClient, which already rebuilds an owned client (relay-active included) from the saved row after re-pairing, instead of a savedAddressChanged flag on forceReconnect. The session establisher, the only relay dialer, holds the relay and adopts learned routing through setRelayRouting; the supervisor takes a host id and a required relay, so the no-relay guards and the synthetic upgraded profile go. enqueueHostListMutation returns its operation's value, and the overlay store names its API after routing. Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> Co-authored-by: Neil <neil@stably.ai> --------- Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
c5f33bd139 | fix(ipynb): run no workspace interpreter until the notebook is trusted (#22962) | ||
|
|
a00c424a11 |
Preserve pending SSH terminal layout edits (#22991)
Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
ff74506c0b |
fix(native-chat): keep terminal pane chat ownership stable (#22984)
Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
fa018fe520 |
chore(deps): refresh maintained dependencies (#22964)
* chore(deps): refresh maintained dependencies * fix(deps): defer upgrades that violate runtime and test contracts * test: align catalog response assertion and updated formatter |
||
|
|
8846987c99 |
feat(rate-limits): add Cursor usage tracking (#22633)
* feat(rate-limits): add Cursor usage tracking ## ELI5 If you use Cursor, Orca now shows how much of your monthly Cursor plan you have used, next to the Claude, Codex and Grok meters, and in Settings → Accounts. It reads the sign-in Cursor already saved on this computer and never changes it. ## What changed Cursor becomes a rate-limit provider like Grok: a status-bar meter (default-on, with its own toggle), a row in the usage roster, and a Settings → Accounts section naming the signed-in account. The credential is read from whichever of three stores has it, first match wins, all read-only: - the macOS login keychain item `cursor-access-token` / `cursor-user`, which is where `cursor-agent` 2026.06+ keeps the session; - `~/.cursor/auth.json` and its platform variants, used by older CLIs; - the Cursor IDE's `state.vscdb` (`cursorAuth/accessToken`), for people who never run the CLI. The keychain entry is the one current CLIs use, and reading only `auth.json` finds nothing on an up-to-date macOS install. A locked keychain cannot mask a readable `auth.json`, and a locked `state.vscdb` cannot mask either. `~/.cursor/cli-config.json` supplies the account's email and display name; it never holds a token. Usage comes from the dashboard route the Cursor web dashboard itself reads, because Cursor documents no individual-user usage API — every documented API is team- or Enterprise-scoped. Per Cursor's pricing docs an individual plan has two pools, Cursor Models and Other Models, both resetting with the billing cycle, plus optional on-demand spend; each becomes a named bucket. The headline percentage prefers `used / limit` over the sibling percentage fields, which are pre-rounded for the dashboard's own copy. Because the route is undocumented the mapping is defensive: an unrecognised payload resolves to `unavailable` and hides the bar rather than publishing a zero that reads as "no usage". Orca never runs `cursor-agent login` and never writes, refreshes or rotates a Cursor credential. An expired token short-circuits to an actionable "run cursor-agent login" instead of spending a request that can only 401 — not a rare case, since `cursor-agent status` still reports `isAuthenticated: true` against a token that expired months ago. ## Why this shape Six open PRs implement this feature and none reads the keychain, so each finds nothing for a large share of users; this takes the auth layer further and keeps what those PRs verified live. The bar is not gated on `cursor-agent` being on PATH, unlike other CLI providers, because an IDE-only session is real usage with no CLI to detect. `readKeychainPassword` moved out of the Claude keychain reader into `src/main/macos-keychain/generic-password.ts` so both providers share one `security(1)` wrapper. It is a byte-for-byte relocation, so Claude's credential path is unchanged; the two child_process allowlists move the entry with it and neither ratchet count changes. Co-authored-by: Preschian Febryantara <preschian@users.noreply.github.com> Co-authored-by: Qwesdy <qwezdi@proton.me> Co-authored-by: ivo922 <github.concur614@passmail.net> Co-authored-by: Mihail Vratchanski <mivrkiki@gmail.com> Co-authored-by: Tauri-EPO <enrico.pin@gmail.com> Co-authored-by: Raajik <44516546+Raajik@users.noreply.github.com> * test(rate-limits): name the JWT helper's segment type in the Cursor tests The anti-slop gate rejects a bare `object` parameter; the fixtures build a claims record, so say that. * fix(rate-limits): render Cursor's pools and keep its plan total visible Review of the first commit found the meter effectively blank for a healthy account, which the screenshots missed because the only Cursor session on hand had expired and never reached the success path. - The verbose status-bar segment filtered buckets through an allowlist written for Gemini's experimental models, so both Cursor pools were dropped and the fallback needed a `session` window Cursor never reports. A signed-in account rendered an icon and no number. The allowlist now admits Cursor's pools, and the fallback accepts a monthly window. - `getWindowSections` dropped `monthly` whenever buckets existed. Cursor puts the plan total there and its sub-pools in buckets, so a plan at 92% showed as 50% in the roster, the tooltip, and the tightest-usage pick. - A plan reporting `enabled: false` still published its 0% pools, painting a healthy meter for a pool the account does not own and skipping the request-quota fallback. - `redirect: 'error'` turned the dashboard's bounce to /login into a generic network failure, hiding the actionable sign-in message. - A busy `state.vscdb` (the IDE holds it open) surfaced as a provider error, which would pin an alert bar on Cursor IDE users who never set Cursor up in Orca. It falls through to "no credential" instead. - Refreshing the Accounts section read the keychain twice for one update. * fix(rate-limits): pin the platform in the Cursor keychain tests Review caught three cases that assumed macOS: the keychain source is behind an explicit `process.platform` check, so on the Linux CI runner the mocked read was never reached and the tests read the CLI file instead. They now set the platform they mean, and two new cases assert the off-macOS fall-through. Also track the credentials reference doc (docs/** is ignored by default, so a new reference needs its own allowlist entry) and give the visibility fixtures their own provider id instead of Grok's. * fix(rate-limits): prefer a live Cursor session and report a failed refresh Review round two, from CodeRabbit and Pullfrog. - Credential precedence returned the first token that parsed, so an expired keychain token in front of a fresh Cursor IDE session reported "sign-in expired" on every poll while a usable session sat one source below. A live session now wins; the expired one is returned only when nothing live exists, so the actionable message still appears in that case. - The usage schema took `.optional()` where the route sends `null` for an absent sub-object, so one null pool failed the parse for the whole body and threw away valid pools and the billing cycle with it. - Cursor usage could survive an account switch: a failed refresh for account B kept account A's figures beside B's name in Accounts. The snapshot now carries a hashed account fingerprint, and a known-and-changed identity clears the previous reading. A refresh that names no account still keeps its own. - The Accounts section rendered nothing at all when a signed-in account's fetch failed, and could repaint an older account when two status reads overlapped. It now states the failure — beside the numbers when a stale snapshot remains — and ignores superseded reads. - A web client claimed "not signed in" for a host it cannot read, contradicting the meter beside it; it now says the detail is host-only. - Signed-out copy named `cursor-agent login` as the only way in, though an IDE sign-in works just as well. - The census comment ended at 4219 after the pacer squash without naming the two modules #22616 added; recorded them, re-measured on a clean origin/main. - Narrowed the docs claim: Cursor documents all-plan APIs, but no individual usage endpoint. * fix(i18n): localize the web client's Cursor host-only notice It reaches the Accounts pane like any other string, so the coverage gate is right to want it in the catalog rather than allowlisted. * fix(rate-limits): name the Cursor account on failed refreshes, and ship the reworded copy Review round three. Both findings say an earlier fix did not actually take. - The account-switch guard reads `authProvenance` off the fresh result, but the fetcher stamped it only on success and network failures. The `stale-token`, 429, 5xx and parse results omitted it, and so did the expired-session branch — so a switch whose first refresh failed, which is precisely the case the guard exists for, still rendered the previous account's figures under the new name. Every failure holding a readable session now names its account; a missing or unreadable credential still names none. The service test also fed a result shape the fetcher never produces, so it proved nothing; it now uses the real stale-token shape, and the fetcher test asserts provenance across 401/429/5xx and expiry. - The reworded signed-out copy never rendered: a present catalog value beats the `translate()` fallback, and `sync:localization-catalog` only adds missing keys rather than updating changed defaults. Updated both strings in en.json, which also prunes them from the runtime-required catalog now that they match. * docs: keep the Cursor credentials reference out of the tree Its content lives in the PR description instead; docs/** stays ignored rather than gaining an allowlist entry for this branch. * test(mobile): drop the census note main no longer pins main removed `SESSION_ROUTE_MODULES` and re-pinned this lane on a different count, so the paragraph this branch added documents a number series that is gone. The branch touches nothing in this file now. --------- Co-authored-by: Preschian Febryantara <preschian@users.noreply.github.com> Co-authored-by: Qwesdy <qwezdi@proton.me> Co-authored-by: ivo922 <github.concur614@passmail.net> Co-authored-by: Mihail Vratchanski <mivrkiki@gmail.com> Co-authored-by: Tauri-EPO <enrico.pin@gmail.com> Co-authored-by: Raajik <44516546+Raajik@users.noreply.github.com> |
||
|
|
1c2cf120e3 |
fix: stop process-tree loops (#22411)
Based on the report and proposal by @brynnclaw. Co-authored-by: brynnclaw <brynnclaw@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
16784c1a67 |
fix(native-chat): name a chat write by its target, not the owner generation (#22812)
* refactor(native-chat): remove the unused terminal handoff No client ever called agentSession.requestHandoff or mounted the handoff chrome. Delete the handoff coordinator, the terminal-owner runtime, the proof write path and the unmounted UI. Keep agentSession.handoffStatus, which released desktop clients read for worktree activation, and let records an older build left mid handoff reconcile through the ordinary restart and recovery paths. * fix(native-chat): never let the pre-stop snapshot hold a chat's stop Eviction now drains delivered events before quit's resume-offer snapshot. An unbounded wait there sits ahead of the provider stop, so a sink whose journal write stalls kept the child running until the step deadline aborted the eviction. The offer is advisory: bound the drain and stop the child regardless. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop helpers only the terminal handoff called `claudeAuthEnvCarriedForward`, `isPathWithinDirectory` and `queryWindowsProcessRowsFresh` lost their last caller with the handoff. The fresh-scan tests now go through `queryWindowsProcessDescendants({ fresh: true })`, the teardown path that still depends on that contract. Co-Authored-By: Claude <noreply@anthropic.com> * docs(native-chat): stop citing the removed handoff in lifecycle comments Six comments still named the handoff coordinator, a handoff suspend, or a terminal-owned session as live participants in the flows they describe. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stalled snapshot drain without a cast Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that a start dead before proving owes no settlement The removed restart handoff test pinned this branch; nothing else did. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): keep the owner-status read behind an in-flight attach The handoff removal dropped the per-session queue from `handoffStatus`, so a read landing mid-start reported the reservation (no owner) instead of the settled chat owner, and shipped desktop clients blocked worktree activation on it. The read is queued again, as it was before the removal. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(terminal): remove the agent-session PTY write gate The gate only refused a write when a PTY had been bound to a chat session, and the only code that ever bound one was the terminal handoff this branch removes. With it gone, every admit/readmit returned "admitted" unconditionally, so the checks on the renderer write path, the runtime controller backstop, terminal.send, agent prompts, preview input and orchestration pointers, the refusal fields on terminal.send and worker-start receipts, the plugin and CLI refusal copy, and the adopted-pane orchestration routing could no longer run. Ordinary writes take the same path in the same order as before. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop the transcript helpers only the handoff called appendLegacyTranscriptMessages fed the terminal transcript catch-up and proveClaudeTranscriptBranch backed the terminal owner's exit proof. Both lost their last caller with the handoff. Their tests now go through the live entry points instead: the roster bounds through the legacy import, the pinned-read and growth tests through the ancestry replay the history window uses, and the marker rules through the string proof in their own file rather than the session-file resolver's. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): stop calling a starting chat "mid-handoff" A send refused because the chat's owner is not settled showed "The session is mid-handoff (<stage>)." in the composer. With the handoff gone, the stages that reach it are a chat that is still starting, or one whose previous agent process has not yet been confirmed stopped. The message now says which of the two it is. The refusal code is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stand-in roster decoder without a cast Co-Authored-By: Claude <noreply@anthropic.com> * refactor(codex): name the pinned rollout lookup for what it does With the terminal handoff gone, the module named codex-tui-rollout-proof holds only the pinned rollout lookup that structured Codex launches use to resume a thread, so the name described code that no longer exists. Rename the module and its options type. Also drop a mobile allowlist assertion that pinned the removed agentSession.requestHandoff method, which no longer exists to allow. * refactor(native-chat): type the owner-status reply as the host sends it The handoffStatus reply type still listed the terminal handoff's fields and states (terminal placement, host label, proof retry, queued and waiting phases, the to-terminal direction). No host writes them any more and the only client reader parses the reply as unknown, so they described nothing. The reply on the wire is unchanged. * refactor(native-chat): normalize terminal-handoff lease values once at decode Nothing in this build writes a terminal owner (`runtimeKind: 'tui'`) or the handoff's `preparing` / `old-owner-stopped` stages, but the in-memory types still admitted them, so readers across the host kept branches for values no path produces and the compiler could not point at them. The store now validates the on-disk shape, which still accepts those values so an older record is not quarantined, and maps them once while parsing: - `preparing` and `old-owner-stopped` become `recovering` - a `tui` lease becomes `native`; when it records a process it also becomes `conflicted`, the claim every build probes but never stops. A plain native owner would be stopped by restart recovery, here and in older builds. Revisions are taken over the normalized state on both sides of every compare, and the mapped record reaches disk with the store's first transaction, the same way the tab-id backfill does. The in-memory types narrow to what this build writes, and the branches that existed only for the removed values go. Structured-worker identity keeps its verdict for a former terminal owner by refusing a conflicted claim rather than a non-native kind. * refactor(native-chat): stop threading the owner kind through a reservation A reservation only ever names a native owner now, so the request no longer carries a kind and the reserved lease records `native` directly. The attach params keep `runtimeKind`: agentSession.ensure and create accept it, and the operation fingerprint stored in the ledger covers it. * test(native-chat): pin the legacy-lease rewrite with a transaction that changes nothing else Hiding a tab also committed the visibility index, so the no-op transaction wrote the file even when its open-time revision was wrong. Committing the index first leaves the pending rewrite as the only reason to write. * fix(native-chat): name a chat write by its target, not the owner generation A write carried the fence of the last frame the pane read, and the host refused it unless that fence was still current. An idle release and the restart after it each move the fence, and the release publishes nothing, so a send after a release was refused "Expected runtime fence 1; the session is at 3", and a Stop queued behind a cold start was refused as stale. Every write already names what it acts on: a send its conversation, a cancel its turn, a prompt answer its item revision, a rewind its epoch; an option is last-writer-wins. So admission stops comparing the client's fence, and the rebase that papered over one restart (admitAtResumedFence, resumedFromFence) goes with it. The writer-lease check stays, and so does the attach's compare-and-swap. Frames now stamp the fence read when each frame is sent instead of a copy each subscriber kept, which went stale on the same release. * docs(native-chat): say mutation admission checks only the writer lease * docs(native-chat): drop the send rebase from comments that still described it * fix(native-chat): keep each pane's own fence on frames so a failed restart is not resent * docs(native-chat): drop the fence from the admission the send effects run behind * docs(native-chat): give the fence move on release the reason that still holds * docs(native-chat): stop citing a write fence check in launch and mailbox comments Three places still gave the removed fence check as a reason: the launch replay said admission puts the ledger ahead of the fence, the launch surface said a send must name the lease it was admitted against, and the direct-mailbox path said the lease fence decides whether delivery is safe. Admission now checks only the writer lease. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
e785cacb67 |
fix(worktrees): keep failed orphan cleanup retryable (#22409)
Co-authored-by: SiinXu <SiinXu@users.noreply.github.com> |
||
|
|
19d472fad8 |
fix(native-chat): report a chat's owner from its record, not its running agent (#22808)
* fix(native-chat): report a chat's owner from its record, not its running agent Released desktop clients gate worktree activation on agentSession.handoffStatus and count a chat tab as claimed only when the owner is `native`. The host answered `native` only for a live lease, and threw not_attached for a chat idle release had forgotten, so a chat at rest (idle-released, or restored after a restart) blocked its whole worktree from activating. The answer now comes from the record store for any record this host supports: the owner is the lease's runtime kind whatever its liveness, and manual recovery still answers `none`. With no map entry needed and nothing to wait for, the serialized read that kept a mid-start chat's answer honest goes too. * test(native-chat): pin the two owner answers that still refuse to vouch With liveness gone, the manual-recovery branch and the unsupported-record refusal are the only paths that keep a chat from answering native; neither had a test. * refactor(native-chat): say plainly why an unsupported chat reports no owner * test(native-chat): say what a blocked activation gate actually skips |
||
|
|
600adba9f0 |
fix(native-chat): every journal append reaches the chats that are open (#22811)
* refactor(native-chat): remove the unused terminal handoff No client ever called agentSession.requestHandoff or mounted the handoff chrome. Delete the handoff coordinator, the terminal-owner runtime, the proof write path and the unmounted UI. Keep agentSession.handoffStatus, which released desktop clients read for worktree activation, and let records an older build left mid handoff reconcile through the ordinary restart and recovery paths. * fix(native-chat): never let the pre-stop snapshot hold a chat's stop Eviction now drains delivered events before quit's resume-offer snapshot. An unbounded wait there sits ahead of the provider stop, so a sink whose journal write stalls kept the child running until the step deadline aborted the eviction. The offer is advisory: bound the drain and stop the child regardless. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop helpers only the terminal handoff called `claudeAuthEnvCarriedForward`, `isPathWithinDirectory` and `queryWindowsProcessRowsFresh` lost their last caller with the handoff. The fresh-scan tests now go through `queryWindowsProcessDescendants({ fresh: true })`, the teardown path that still depends on that contract. Co-Authored-By: Claude <noreply@anthropic.com> * docs(native-chat): stop citing the removed handoff in lifecycle comments Six comments still named the handoff coordinator, a handoff suspend, or a terminal-owned session as live participants in the flows they describe. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stalled snapshot drain without a cast Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that a start dead before proving owes no settlement The removed restart handoff test pinned this branch; nothing else did. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): keep the owner-status read behind an in-flight attach The handoff removal dropped the per-session queue from `handoffStatus`, so a read landing mid-start reported the reservation (no owner) instead of the settled chat owner, and shipped desktop clients blocked worktree activation on it. The read is queued again, as it was before the removal. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(terminal): remove the agent-session PTY write gate The gate only refused a write when a PTY had been bound to a chat session, and the only code that ever bound one was the terminal handoff this branch removes. With it gone, every admit/readmit returned "admitted" unconditionally, so the checks on the renderer write path, the runtime controller backstop, terminal.send, agent prompts, preview input and orchestration pointers, the refusal fields on terminal.send and worker-start receipts, the plugin and CLI refusal copy, and the adopted-pane orchestration routing could no longer run. Ordinary writes take the same path in the same order as before. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): drop the transcript helpers only the handoff called appendLegacyTranscriptMessages fed the terminal transcript catch-up and proveClaudeTranscriptBranch backed the terminal owner's exit proof. Both lost their last caller with the handoff. Their tests now go through the live entry points instead: the roster bounds through the legacy import, the pinned-read and growth tests through the ancestry replay the history window uses, and the marker rules through the string proof in their own file rather than the session-file resolver's. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): stop calling a starting chat "mid-handoff" A send refused because the chat's owner is not settled showed "The session is mid-handoff (<stage>)." in the composer. With the handoff gone, the stages that reach it are a chat that is still starting, or one whose previous agent process has not yet been confirmed stopped. The message now says which of the two it is. The refusal code is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): type the stand-in roster decoder without a cast Co-Authored-By: Claude <noreply@anthropic.com> * refactor(codex): name the pinned rollout lookup for what it does With the terminal handoff gone, the module named codex-tui-rollout-proof holds only the pinned rollout lookup that structured Codex launches use to resume a thread, so the name described code that no longer exists. Rename the module and its options type. Also drop a mobile allowlist assertion that pinned the removed agentSession.requestHandoff method, which no longer exists to allow. * refactor(native-chat): type the owner-status reply as the host sends it The handoffStatus reply type still listed the terminal handoff's fields and states (terminal placement, host label, proof retry, queued and waiting phases, the to-terminal direction). No host writes them any more and the only client reader parses the reply as unknown, so they described nothing. The reply on the wire is unchanged. * refactor(native-chat): normalize terminal-handoff lease values once at decode Nothing in this build writes a terminal owner (`runtimeKind: 'tui'`) or the handoff's `preparing` / `old-owner-stopped` stages, but the in-memory types still admitted them, so readers across the host kept branches for values no path produces and the compiler could not point at them. The store now validates the on-disk shape, which still accepts those values so an older record is not quarantined, and maps them once while parsing: - `preparing` and `old-owner-stopped` become `recovering` - a `tui` lease becomes `native`; when it records a process it also becomes `conflicted`, the claim every build probes but never stops. A plain native owner would be stopped by restart recovery, here and in older builds. Revisions are taken over the normalized state on both sides of every compare, and the mapped record reaches disk with the store's first transaction, the same way the tab-id backfill does. The in-memory types narrow to what this build writes, and the branches that existed only for the removed values go. Structured-worker identity keeps its verdict for a former terminal owner by refusing a conflicted claim rather than a non-native kind. * refactor(native-chat): stop threading the owner kind through a reservation A reservation only ever names a native owner now, so the request no longer carries a kind and the reserved lease records `native` directly. The attach params keep `runtimeKind`: agentSession.ensure and create accept it, and the operation fingerprint stored in the ledger covers it. * test(native-chat): pin the legacy-lease rewrite with a transaction that changes nothing else Hiding a tab also committed the visibility index, so the no-op transaction wrote the file even when its open-time revision was wrong. Committing the index first leaves the pending rewrite as the only reason to write. * fix(native-chat): every journal append reaches the chats that are open A journal write and its delivery to open readers were two calls, and some writers made only the first. A failed start whose lease could not be handed back, a provider revision with no frame behind it, and eviction's settlement were all journaled without reaching an open chat. A journal handle now reports every durable change, and the host's session map binds that report to the session's readers when the handle is set. Writers no longer publish what they append; the per-writer publish calls are deleted. * test(native-chat): an epoch replacement reaches the open chat * test(native-chat): each row reaches an open chat once, and a live handle enters only through the map * perf(native-chat): a publish behind a delivered commit reads nothing Each commit now delivers itself, so the publish a provider frame still sends afterwards found every reader caught up but still read rows and rebuilt the timeline for each one. A caught-up reader now skips the read. * test(native-chat): state why the teardown test's fake journal is safe to cast --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
8d21fdbf60 | fix(mobile): update fflate for security advisory (#22961) | ||
|
|
77829142ac |
fix(mobile): a terminal opens once at the phone's size, on the page and natively (OTA phase C follow-up) (#22960)
* refactor(mobile): extract the once-per-route terminal viewport measure Behaviour-preserving move of measureViewportOnce into a pure module so the first-subscribe ordering can be tested without mounting the session route. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): send phone dims on a terminal's first subscribe A fresh terminal document has no xterm until its first init, so the viewport measure could not answer before the first subscribe. The subscribe went out without dims, the host serialized the snapshot at the desktop's size, the phone painted it, measured, resubscribed and replayed the whole snapshot a second time at the phone's size. The first subscribe of each document now opens an empty terminal, measures, and only then subscribes, so the host fits the PTY before serializing and the phone paints once. Measure failure still subscribes without dims and the existing fit pass takes over. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): converge the fit pass on the viewport the subscribe sent A frame-height refit can clear the measured flag after the first subscribe has already carried the phone's viewport. The fit pass then treated the snapshot as dimensionless and resubscribed, replaying the whole snapshot a second time at the same size. It now judges convergence against the viewport that subscribe actually sent; the refit still owns real layout changes through updateViewport. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): reject the failing measure without an async wrapper Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): keep the fit-pass decision on the measured flag Converging on the viewport a subscribe carried accepted a stale one: after native chat covered the terminal through a rotate or dock, the return resubscribe sent the old dims, the host matched them and the pass never re-measured (#4579). The decision is back on the measured flag; the sent viewport only lets a matching fresh measure skip the round trip. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): forget a document's first-subscribe mark only on web-ready The terminal handle re-attaches null then new on every theme or text-size change, and clearing the mark there let the next resubscribe post an empty init onto a live document. web-ready is the one signal that a new document started, so the mark is dropped there instead. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): pin each held-subscribe teardown guard on its own Splits the teardown test so dropping either the generation check or the in-flight check alone fails a test, and shares one MutableRef type. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
18bbf6f209 |
refactor(orchestration): resolve every caller and target to one orchestration party, keyed by the Orca session id (#22555)
* refactor(orchestration): resolve a session caller at the dispatch entry and bind it by actor WIP: entry resolver on both dispatchers, caller identity through run scope, actor-keyed Run binding and unbind, actor writes on bind/create/assign, and actor-aware mail ownership exclusions. * test(orchestration): pin actor-keyed Run binding, stale-actor precedence and actor mail ownership Keeps the non-session dispatch path synchronous so terminal and session-tab streams reach their handler without an extra async hop. * feat(orchestration): resolve session callers before params parse and pin every verb on both routes A session caller need not name itself in a param that requires a caller: the entry binds the declared caller to the session before the schema runs. Session refusal codes pass through the RPC error map, and DB row reads added here carry their SAFETY rationale. * test(orchestration): pin the SSH check's pane through the caller-identity lookup * test(orchestration): pin a Run-less session's direct check and receipt binding without a caller param Drops the actor clause from self-dispatch detection: a creator and assignee can only share an actor when they already share a handle or pane. * test(orchestration): pin a session's own Run for plain and group sends and the assignee-only mail sweep * test(orchestration): name the party-naming field population for its role * fix(orchestration): clear a worker actor an older binary's unbind leaves, and refuse a worker without its identity An older binary unbinds a structured worker's Run by clearing handle and pane, which leaves the actor looking like a handle-less chat binding. The every-open repair clears that shape for actors recorded as structured workers only, and the resolver refuses a worker session whose worker identity is gone, so this binary never writes the shape itself. * refactor(orchestration): read a Run's coordinator actor through its generation, and give a party's addresses one owner The coordinator actor now counts only at the consumer generation it was written at, so a Run binding matches a session by that rule alone. It replaces two mechanisms for the same fact: the rule that an actor beside a handle it did not bind with never matches, and the open-time repair that cleared a structured worker's actor an older binary's unbind left. Every write of an older binary that rebinds or unbinds bumps the generation, so both shapes stop counting by themselves, including a chat's actor after a rebind then an unbind, which neither old mechanism caught. createRun and the same-coordinator actor correction write the generation in the statement that writes the actor. The resolver still refuses a structured worker whose worker identity is gone; its predicate moves next to the worker identity lookup. addressSpellingsOf is the one owner of the addresses a party is reachable at (a structured worker's handle and session actor). createRun, bindRun, the coordinator unbind and the declared-caller check use it instead of hand-built sets, and each has a test at both of a worker's addresses. * fix(orchestration): say a released session is not running, and scope the pane-key credential claim to requests without a session A released lease is evicted, not ended: a user turn resumes the session, so the refusal now says it is not running right now instead of that it has ended. The worker pane-key comment claimed the random leaf is what stops anyone who learns a session id from acting as the worker. On the same-host socket route the session id now names the worker with no token by design; the pane key still matters where a request names no session (a PTY agent's, or the paired-client route, which refuses session ids). * test(orchestration): pin the same-coordinator actor correction's generation write on a row an older binary wrote * refactor(orchestration): resolve session callers by the bare Orca session id, typed apart from its address Carries the Orca session id rename into caller resolution, Run binding and Dispatch creation. The caller identity holds the bare `orcaSessionId`; the `session:<id>` spelling is derived by formatOrcaSessionAddress wherever mail needs it. `OrcaSessionId` and `OrcaSessionAddress` are distinct branded strings. Only isOrcaSessionId and parseOrcaSessionAddress produce an id, and only formatOrcaSessionAddress produces an address, so comparing the two is a type error. The Orca session id columns on the row types carry the id type. Every reader that compares a stored id with a mail address now compares like with like: the active-Dispatch ownership check formats the stored id (orcaSessionAddressSql), and the stray-mail sweep, the creator nesting lookup and the recorded-worker check bind a parsed or typed bare id. The Run-mailbox ownership check keeps its existing handle comparison beside the session one. * fix(orchestration): accept a worker's ask to every address its coordinator is reachable at A worker's preamble names its coordinator as `session:<id>` when the coordinator is a structured session, but ask only accepted `run:<id>` or the coordinator's terminal handle. A chat coordinator has no handle, and a coordinating structured worker has two addresses, so ask --to the session address was refused as dispatch_run_mismatch. The check now takes the Run's current coordinator addresses from addressSpellingsOf(runCoordinatorKey(run)). * refactor(orchestration): require every caller-identity entry point to be handed the resolved session The resolved session parameter was optional on resolveRunScope, resolveOrchestrationCaller, orchestrationCallerIdentity, resolveDispatchCreator and resolveDispatchCallerWorktreeId, so a method that forgot to pass it would compile and silently treat a chat's session address as a terminal handle. It is now required and typed `OrchestrationSessionCaller | undefined`, so leaving it out is a type error. Every call site already passed it; no behavior changes. * fix(orchestration): deliver mail sent to a session address to the mailbox that session reads A send to session:<id> was resolved like a terminal handle: no live pane, so a chat's current Run was missed (two Runs read as ambiguous), a Run-less chat was refused though it reads its direct mailbox, and a structured worker's session address never reached its Dispatch. Resolve a worker's session address as its handle, a chat's by its bound Run, and fall back to the chat's durable direct mailbox while it runs on this host. * refactor(orchestration): resolve every party through one resolver with one mailbox address A structured worker is reachable at its handle and at its session address, and a chat only at its session address. Callers and targets were each compared or resolved at their own site, some against one spelling and some against every spelling, and the sites that did neither refused or misrouted. Add orchestration-party: resolveOrchestrationParty (and resolveOrcaSessionParty for a bare id) is now the only place an address becomes a party. The session caller, the declared-caller check, ask's target and inbox's filter all resolve through it, so caller and recipient resolution cannot disagree, including on a recorded worker whose identity this host lost. Every session-to-party step passes through canonicalOrcaSessionId, the seam later lineage canonicalization plugs into. mailboxAddressOf replaces addressSpellingsOf: a party has one mailbox address (a worker's handle today), and Run binding remembers and reroutes only that. A request with no session id that declares a session address as its caller now gets the party it names: a worker's handle, as if it had named it, or a session_caller_chat_not_declarable refusal for a chat, which is identified only by the session id its own environment sends. * fix(orchestration): route dispatch and mail targets through the party they name dispatch --to a worker's session address stored that address as the assignee handle, so the worker, which reads by its handle, never saw the Dispatch. The assignee now resolves through the party resolver: a worker's either spelling assigns its handle and records its Orca session id. A chat cannot receive a dispatch yet, so dispatch --to a chat is refused with session_chat_not_dispatchable before any row is written. Recipient routing resolves the party once and, for any session-backed party, finds its Run by its durable binding rather than by a live pane. A structured worker that coordinates a child Run while assigned in its parent got mail at its parent Dispatch mailbox whenever its session was evicted, which it never reads while bound to the child Run. Terminal handles keep the same live-pane lookup. * refactor(orchestration): delete the SQL that matched a worker's second spelling With every caller and target resolved to one mailbox address, no writer stores mail under a structured worker's session address: sends resolve it to the handle, a declared session caller is rewritten to the handle or refused, replies answer a stored canonical sender, and questions, answers, escalations, federation and legacy mail write run:, dispatch: or handle addresses (legacy rows are legacy_direct, which these queries never read). The branches that matched that spelling were unreachable and are removed: - the session-address OR in activeDispatchOwnsAddressSql, back to the assignee handle alone; - the session branches in routeForeignDirectMessagesToOwnedMailboxes and findActiveDispatchForDirectMessageOwner, which return to the base-branch form. Tests that inserted such rows directly now pin the behaviour through the verbs: both spellings of a worker land in the one mailbox it reads. * test(orchestration): pin one canonical address per party across every caller and target - every target param in ORCHESTRATION_TARGET_PARAM, for a worker's handle and session address and a chat's session address: send, ask, dispatch (a chat refused with no row) and inbox; - a declared session-address caller on a request with no session id: a worker acts as its handle, a chat is refused; - a worker that coordinates a child Run while assigned in its parent gets mail in the child Run with no live pane; - no mail writer stores to_handle or from_handle as a worker's session address; - the caller a session id resolves to and the recipient its address resolves to agree in every party state, and every session-to-party step goes through canonicalOrcaSessionId. * refactor(orchestration): drop the session caller's chat-to-terminal-view handoff remnants The structured-chat terminal handoff is gone from the base branch: a session is owned natively only. - the lease rule no longer speaks of either owner or a handoff keeping identity; - an in-progress owner change (new-owner-proving, recovering, manual-recovery, which the base branch still produces) is refused as changing owners, not as switching between chat and terminal view; - the native to terminal-view to native identity test is deleted, and the terminal-evidence test no longer describes that evidence as a terminal view's. * fix(orchestration): read a declared caller param by typed access, not Reflect.get |