mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
d0baa20d0ec002699abd82d577d13a506b5fee07
529
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
982570648a | fix(wsl): refresh hook relay on PTY reattach (#13139) | ||
|
|
17b3dff3c4 |
refactor(terminal): return IME composition ownership to xterm (#13128)
* fix(terminal): return IME composition ownership to xterm * fix(mobile): derive terminal input from native replacement ranges * test(mobile): record iOS Japanese IME traces * fix(mobile): preserve native IME replacement ranges * fix(xterm): flush queued application input after IME commit * test(terminal): pin Korean intermediate commit * test: pin Windows IME shortcut ownership * test: replay IBus number candidate commit * fix: preserve native macOS input-method punctuation * refactor(terminal): remove stale mac focus override * fix(mobile): preserve soft keyboard deletion ranges * fix: keep IME-owned palette chords in renderer * fix: stop carried IME shortcuts at renderer owner * fix: preserve carried IME shortcut dispatch * fix: narrow main-owned shortcut actions * test(mobile): pin Japanese IME replacement traces * test(terminal): retain paired native IME trace * fix(chat): preserve browser IME composition ownership * fix(chat): retain macOS IME confirm gesture * fix(chat): expire unmatched IME confirm carry * fix(chat): isolate IME confirmation expiry * fix(chat): retain active IME confirmation * refactor(terminal): remove dead composition handler * feat(ime): add shared Enter-ownership seams for CJK composition The confirming Enter of a CJK composition arrives as two keydowns and the orderings differ by platform: Windows/Linux redispatch the unmarked Enter/13 before keyup, macOS delivers keyup first. A guard reading only isComposing or keyCode 229 misses the redispatch, so surfaces submitted on a confirm. Adds useImeEnterGestureOwnership (carry token, next-frame expiry), a shared ImeEnterGuardedForm for native implicit submission, and the cmdk seam covering 18 CommandInput surfaces at one site. A chorded Enter arms the carry but is never swallowed — the reverse would eat a user's deliberate Cmd/Ctrl+Enter. Both failure modes are pinned by ime-enter-gesture-ownership-contract.test.ts. Co-authored-by: Orca <help@stably.ai> * refactor(terminal): consolidate native input listeners and parked-screen owner Extracts the shared native-input listener installer and renames the parked-screen detector for what it actually does, replacing per-call-site duplication. The listener installer keeps a forgetOptionKeyLocationOnBlur flag so per-window semantics are preserved rather than flattened. Net deletion; no behaviour change intended. Co-authored-by: Orca <help@stably.ai> * test(terminal): pin recorded IME shapes as regression tests Nine regression tests built from hashed affected-platform captures, each with a paired ordinary negative and a discriminating mutation verified to take the file from all-passing to exactly one failure. Covers the Windows MS-Korean Shift family (#12179, #11878, #12151, #11946, #12152) and the Korean TUI line-break rows (STA-3237, STA-3222, STA-3129). STA-3237 pins the empirical 3-Shift / 2-active-composition / 2-newline ratio the device run established — the third Shift produces nothing because Space has already committed. That ratio is not derivable from a static capture. Co-authored-by: Orca <help@stably.ai> * fix(ime): guard Enter-commit surfaces against CJK confirm Applies the Enter-ownership guards across the surfaces whose Enter commits something: publishes, clones, pairs, installs, posts, or persists. Tiered deliberately rather than uniformly. Irreversible and remote-effect sites take the carry token, which also blocks the unmarked redispatch. Locally reversible sites take the oracle check with a one-line comment naming the residual, because a spurious commit there costs one undo. Three numeric fields are left unguarded with the reason in-code: Chromium blanks number inputs at compositionstart, so a confirm-Enter only ever reaches an empty-draft reset. Measured with a CDP probe rather than assumed — a guard that cannot fire is noise. Co-authored-by: Orca <help@stably.ai> * test(ime): teeth-check the Enter guards on every guarded surface One suite per guarded surface, each verified by deleting the guard and confirming the test fails. A green guard test without that check is unverified, not verified. Two shapes pass vacuously in happy-dom and are avoided here: native implicit form submission never fires, and blur() is inert on an unfocused element. Both made "the commit did not happen" assertions pass with the guard removed, so the suites assert the guard's contract directly instead. Co-authored-by: Orca <help@stably.ai> * fix(mobile): keep iOS Korean commits whole through the live-input path iOS Korean reports isComposing: false on every event, so it bypasses the composition guard entirely. The strict owner rejected UIKit's transformed post-change field and sent only the leading jamo — the reported symptom. Prefers the authoritative same-event field text over the predicted text when the supplied operation cannot produce it. Generic: no Korean special-case, no locale classifier, no normalization. Adds the RN-target-keyed submit carry alongside it. Co-authored-by: Orca <help@stably.ai> * test(e2e): make IME capture harnesses fail loudly instead of silently Four instruments recorded silence as success, so a void run scored as a clean one: - readTerminalImeBoundaryTrace returned an empty trace when the probe never installed, making every "nothing leaked" negative pass vacuously - summarizeLatencies([]) returned a perfect zero distribution that passed all three latency thresholds - the macOS Vietnamese spec pinned an input-source ID that does not exist, and failed as though the operator had chosen the wrong source - the expectedLineCount=1 prefix property was undocumented and one edit from silently downgrading a PTY assertion Input sources now resolve by enumeration and name the near-matches on failure. Co-authored-by: Orca <help@stably.ai> * test(terminal): cover Cangjie cancellation and fix a cross-namespace assertion Adds #11951's recorded Cangjie cancel shape to the existing cancellation suite, which covered Pinyin and Sogou but not Cangjie. One keystroke then Backspace arriving as deleteContentBackward with data: null, so the stale preedit is the only thing a fallback could replay. Verified against the historical pre-6cd944c62b3 bundle: the positive fails with ['尸'] where [] is expected, while the ordinary negative stays green. Also fixes the Vietnamese spec, which asserted a TIS-space input-source ID against getKeyboardInputSourceId(). Those two Orca APIs report the same source in different namespaces — TIS nests it under VietnameseIM, the app API does not. The resolver stays as an installation precondition; the assertion matches the leaf. Co-authored-by: Orca <help@stably.ai> * test(e2e): add a real-IME macOS arm for the Korean chord commit The existing korean-ime-terminal-shift-enter-commit spec synthesizes composition over CDP: Input.imeSetComposition sets the preedit directly and Input.insertText performs the commit. Asserting the IME produced events you injected yourself is circular, so that spec cannot certify real-IME behaviour. This arm selects 2-Set Korean via TIS, reads it back live, and injects through System Events key codes, so the OS owns the preedit, the commit instant, and isComposing. PTY byte expectations are preserved verbatim. Covers 2 of the original 4 cases by design. The other two are the Windows/Linux redispatch-before-keyup ordering, which macOS cannot produce and which cannot be selected -- the OS decides it. Reintroducing synthesis to "restore coverage" would reintroduce the circularity. Co-authored-by: Orca <help@stably.ai> * test(e2e): assert the macOS chord arm at the PTY boundary, not the renderer The byte expectations were transcribed from korean-ime-terminal-shift-enter-commit :364/:383, which assert against onData -- a renderer boundary where the terminator is CR. This spec reads the PTY child, where the tty has already converted CR to LF. Names both forms per row rather than swapping the constant, so the conversion reads as evidence that the capture reached past the renderer, as #11936 and #11951 record. Ctrl+Enter's CSI-u sequence is unaffected and is identical at both boundaries. Co-authored-by: Orca <help@stably.ai> * test(e2e): measure composer-to-onData latency and stop dropping IME keystrokes Two defects in the echo latency probe. It hooked onWriteParsed and onRender but never onData, so it measured key->parse->render echo rather than the composer-vs-onData delta the latency rows need. Adds a third hook feeding its own sample set. And `event.key.length !== 1` silently dropped IME keystrokes: Pinyin and Cangjie keydowns arrive as key:'Process' (length 7). Replayed over the captured corpus, the old filter accepted 580 of 4137 Chinese IME keydowns -- it was discarding 80% of them. The new filter matches the shape the owner itself branches on. Attribution charges each onData to the latest keydown rather than a FIFO head, because composing jamo emit no onData at all and a queue would credit a whole composition to its first keystroke. The consumer now asserts sample count before any percentile, so a zero-sample run cannot render as a flawless distribution. Co-authored-by: Orca <help@stably.ai> * test(terminal): pin the WSL shifted-jamo newline shape for #11919 In Korean 2-set, Shift types ordinary letters -- the double consonants and the compound vowels. Each such keystroke reaches Chromium as key='Process', keyCode=229, shiftKey=true. The v1.4.163 classifier matched exactly that pattern with no code guard, so it called those keystrokes Enter, rewrote them to a synthetic Shift+Enter, and injected a newline into the middle of the word -- with no Enter key pressed. That is why the reporters said "no modifier key pressed": they had not chorded Shift+Enter, but they had pressed Shift, to type the double consonant. Asserts the row's own recorded capture: 40 immediate keydowns, exactly 3 of them Shift-carrying inside a single syllable, and an onData stream with one newline per Enter press and none mid-word. Two ordinary negatives keep it from being a blanket mute -- the same session's non-IME keydowns still reach shortcut policy, and an ordinary Shift+Enter still resolves through the real policy. Co-authored-by: Orca <help@stably.ai> * test(terminal): pin the composition commit lag that made Korean type one behind macOS Korean 2-Set commits syllable N only when the first jamo of N+1 arrives, so compositionend and compositionstart land in the same task. A composition-start handler cancelled the pending finalizer that was the only path to triggerDataEvent and ended the session without emitting bytes, so every committed syllable reached onData exactly one syllable late and the backlog cleared only at a Space or Enter. Types continuously with no Enter and no Space -- either would flush the backlog and hide it -- and samples onData at every syllable boundary. Paired with a length-matched ASCII arm that stays green throughout, so the positive is a fact about composition rather than about timing in general. Bisected to a single call site across five builds: pristine, 1.4.155 and 1.4.162 pass, 1.4.163 fails, removing the one call repairs it, restoring it fails identically. That window is exactly the reporter's "started immediately after updating". Co-authored-by: Orca <help@stably.ai> * test(mobile): cover the send-queue abort that silently drops queued keystrokes One failed send in use-terminal-live-input-commit aborts every keystroke queued behind it, with the error swallowed by .catch(() => false). The existing test resolves(true) on every send, so the failure branch was uncovered. Four arms: the abort itself, an ordinary negative on the healthy path, a throwing sender, and a liveness control proving the queue recovers once the chain settles. Deleting the abort takes 4 passed to 3 failed, with the ordinary negative correctly surviving. Scope is stated in the docblock: this is a transport send-queue abort, reachable only via a real disconnect or RPC error. REQUEST_TIMEOUT_MS is 30s, so latency alone cannot reach the branch — consistent with #7094's symptom class, not proven to be its cause. * test(terminal): pin that daemon snapshot/restore cannot disturb a composition Two independent reporters attributed broken Korean composition to the always-on PTY daemon repainting terminal state over the preedit. The attribution is wrong on ancestry — the daemon shipped three months before the version both call good — but the boundary was never actually tested. Runs the real applyMainBufferSnapshot choreography against a live composition, including the full 2J/3J/H wipe plus the resize and alt-screen branches. textarea.value, selectionStart/End, compositionView.textContent and .active all survive byte-identical, and interleaving a restore between every jamo of 문제 still commits 문제 at onData. Also pins that the uncommitted preedit is absent from the captured snapshot: it lives in the textarea, never the buffer, so a restore has nothing stale to echo back. Injecting one textarea.value = '' into the restore fails exactly the three restore-boundary tests. * test(terminal): pin that Cmd tears down a composition where Ctrl and Shift do not xterm's composition keydown exempts only keyCode 16/17/18 (Shift/Ctrl/Alt) plus 20/229. macOS Meta — 91/93/224 — is absent, so a Cmd press mid-composition takes _finalizeComposition(false): the overlay goes dark and never recovers, because compositionstart is not re-fired. The user composes the rest of the word blind. Linux and Windows users press Ctrl and are exempt. xterm already has a Meta-aware modifier predicate in wasModifierKeyOnlyEvent, so this is an internal inconsistency rather than a deliberate choice. Owns no reported row and is version-neutral: 5/5 on both 1.4.162 and 1.4.163. The branch is unexercised in all 328 recorded traces, so this is a hazard pin, not a regression guard. Only the teardown is asserted; the likely duplicated commit needs a compositionend the IME kept alive across the Cmd, which no capture contains. Deleting the exemption fails exactly the three paired negatives; adding Meta to it fails exactly the two Cmd arms. * test(native-chat): characterize preedit loss when a question card replaces the composer An AskUserQuestion card fully replaces the composer by design, but the in-flight composition goes with it: the composer unmounts before compositionend reaches it, so the preedit is never committed to the draft. The committed text survives only because the draft is cached and restored via defaultValue. Node identity changes, value 'abc' is preserved, the 가 is gone. Drives the real NativeChatView -> SessionGate -> InteractiveCard -> questionActive swap -> Composer -> ComposerField, flipped by writing the same store field an AskUserQuestion hook event writes. Flipping questionActive to false fails exactly this test and nothing else across 639 native-chat tests, so the path was entirely unguarded. CHARACTERIZATION TEST: it asserts the loss. Fixing the defect — committing the preedit before the swap, or keeping the composer mounted — will make this file fail. Update the expectations to the new contract rather than working around them. Owns no reported row. #12118/STA-3219 flicker is keyed to token counters, which provably do not remount, and a question card arrives once per question. * test(terminal): pin the duplicated commit when Meta interrupts a composition _finalizeComposition(false) sends textarea.value.substring(start, end) but cannot clear the IME-owned textarea, so a later compositionend re-sends the same range. Meta reaches that path because CompositionHelper exempts only Shift/Ctrl/Alt; xterm's own wasModifierKeyOnlyEvent covers Meta four ways, so the omission is an internal inconsistency rather than a choice. Companion to the modifier-exemption guard, which deliberately pins only the overlay teardown. This pins the data consequence. HAZARD PIN: owns no reported row. The trigger is unverified on hardware — no capture in the corpus contains a Meta-during-composition gesture, and whether macOS keeps the composition alive across it is unmeasured. The duplication follows from the code given that sequence; whether users reach the sequence is the open half. An earlier premise that Space (keyCode 32) reaches this path was refuted by a corpus scan: 0 of 731 evidence files carry a keyCode-32 Space while composing, against 171 at 229, and 229 returns early. * test(terminal): characterize the syllable lost when the textarea blurs mid-composition CoreBrowserTerminal._handleTextAreaBlur clears the helper textarea unconditionally — "Text can safely be removed on blur" — while CompositionHelper._finalizeComposition reads the committed text back out of that same value from a deferred timeout. By the time it runs the value is empty, the substring is '', and triggerDataEvent never sees the syllable. xterm checks composition state in _syncTextArea and omits the same check here. Six cases. Blurring mid-composition loses the syllable in every ordering, including compositionend-before-blur, which is Chromium's real order — so it is not an ordering artifact. A bare textarea.blur() with no Orca code loses it too, which places the owner upstream: Orca's unguarded release on outside pointerdown is one trigger, not the cause. Committing 한 then blurring mid-가 yields ['한'] where ['한','가'] is correct: one syllable gone, surrounding text intact. Teeth checked by inverting — adding an Orca-side composition guard flips exactly the three cases that route through the release path and leaves the bare-blur and no-blur cases green, which is the scope split: a fix in regular-terminal-focus-ownership alone would not close this. HAZARD PIN, but unlike the others this one has a real production injector — clicking outside the terminal mid-composition. Owns no reported row. The shape matches #9738's report; the injector does not, and a shape match with a mismatched injector is not an owner. * test(terminal): say which arm the STA-3237 fixture came from The recorded keydowns are wave 4's A-shift-unmarked-only — the arm that emits no PTY bytes. Nothing in the file said so, so two readers concluded the row's events fail the owner's predicate and that STA-3237 and STA-3222 were different defects. They share an owner; the arm that fires is Process/229+Shift, absent from this bubble-phase trace because the owner claims it in the capture phase. Also corrects "code-blind": the v1.4.163 policy emits \x1b\r only for a shift-only key:'Enter', and a jamo keydown reaches that branch solely via the isTerminalImeProcessEnter rewrite. The mock is deliberately wider so the ownership guard stays under test if that rewrite moves. Comments only — no assertion, fixture value, or mock behaviour changed. * test(e2e): track the input-source selector the macOS specs shell out to Five tracked macOS IME specs ran `swift .tmp/select-input-source.swift`, a file that is gitignored and existed only on one machine. Anyone else checking out the repo — or the same machine after .tmp is cleaned — could not run them, and they are the capture drivers for the macOS rows that are blocked waiting for exactly those runs. Moves it to tests/e2e/ beside its callers. The chord spec now resolves it from __dirname rather than reaching two levels up into .tmp. * test(terminal): pin the CJK repaint decision against the reporter's own output #12164 comment 1 and #5921 report agent output with double-width glyphs rendering duplicated character-by-character while ASCII in the same line stays clean. No IME, no composition, no keystroke — the user never types the CJK. Segmenting all three verbatim samples into maximal same-risk-class runs gives 33 runs and zero violations of "this run is corrupted iff the production detector flags it": 17 wide runs all corrupted, 16 narrow runs all byte-identical. The paired negative is co-located in the same line rather than in a separate run — the reporter supplied it without knowing. Doubling is asserted as present, not uniform: 자바스크립트 and 시스템 each leave a jamo undoubled, which is a repaint-region boundary artifact rather than a per-character transform. The discriminating arm is in the test rather than a source mutation: |
||
|
|
cf16eac7f6 |
fix(agent-hooks): keep Node 18 relay companion loadable (#13135)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
bba32bd00c |
fix(daemon): let the publisher replace a dead endpoint, not a third party (#12882)
Terminals froze app-wide several times daily, needing a manual pkill. libuv unlinks the pathname a server bound to when it closes, with no ownership check, so a departing daemon deleted whichever socket then sat at the canonical path — including a live replacement's. The replacement kept hosting PTYs no client could reach. #12709 fixed that mechanism; this replaces the shape around it. Two invariants: only a daemon publishing itself onto the canonical endpoint may mutate that entry, and only by replacing one it has itself just proven dead; and no actor removes a name it did not create. Publish binds a private name, takes the canonical one with an exclusive link, and on EEXIST proves the incumbent dead by connecting before replacing it in a single rename. Only 'connected' means occupied and only refused/missing prove death — a timeout proves nothing and declines. Deletes the claim sweeper, the reclaim tail of killStaleDaemon, and three unfenced unlinkSync(socketPath) calls in the launcher. Measured: rename exposed no gap across 6,525 darwin / 8,004 linux probes of a live handover, where unlink-then-link gapped on 200 of 200. Verified on all three platforms: full suite on macOS and Linux, and daemon restart e2e on a real windows-2022 host. Contract in src/main/daemon/AGENTS.md. |
||
|
|
de4f272b31 |
fix(i18n): standardize Chinese status bar usage labels (#12881)
* fix(i18n): standardize Chinese status bar usage labels Signed-off-by: ousugo <dkzyxh@gmail.com> * fix(i18n): align Antigravity usage description Signed-off-by: ousugo <dkzyxh@gmail.com> * fix(i18n): standardize the zh status bar usage labels the menu actually renders The status bar item menu renders "<Brand> Usage" for eight providers. Claude, Codex and Gemini read 使用情况; Antigravity, OpenCode Go, Kimi, MiniMax and Grok read 使用量, so one dropdown showed two words for one concept. Register the decision where the repo already keeps it — the zh block of locale-value-overrides.mjs already pins Claude/Codex/Gemini Usage — so the repair pass enforces it instead of the catalog drifting again, and add the missing Kimi entry to BRAND_MISTRANSLATIONS so 基米 can no longer come back. --------- Signed-off-by: ousugo <dkzyxh@gmail.com> Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
c3bf22b9a8 |
[P2] perf(windows): stop the capability poll respawning blocking wsl.exe probes (#11698)
* perf(windows): stop the capability poll respawning blocking wsl.exe probes #11295 added a 30s renderer interval to `useWindowsTerminalCapabilities` whose early-return only fires when WSL is available with at least one distro, so on the common Windows host (no WSL) it re-ran a full capability read forever. Each read IPCs four probes whose main-process handlers were synchronous `execFileSync` calls to wsl.exe/pwsh.exe, blocking the Electron main event loop for up to 5s a time. The un-latching intent is kept: a host that answers "no WSL" is still re-checked, now on an exponential backoff (30s, +60s, +120s) that parks once the answer stops moving, re-arms on window focus, is shared by all consumers of an owner key, and stops entirely when the last consumer unmounts. The wsl/pwsh IPC handlers now use async twins that share the existing caches and back off identically. * fix(windows): classify async wsl/pwsh probe failures with the execFile error shape The async twins feed `execFile` callback errors into classifiers written for `execFileSync`: a non-zero exit lands on `error.code` as a number rather than `error.status`, and a timeout is a SIGTERM kill rather than ETIMEDOUT. So a Windows host without WSL (wsl.exe ships in System32, so it exits non-zero instead of ENOENT) was cached as retryable, shrinking the shared window from 10min to 45s and making the still-sync callers re-pay their blocking spawn ~13x more often; and a pwsh cold start past 5s cached "pwsh missing" for 30s, demoting the user's PowerShell 7 preference — the exact case the ETIMEDOUT branch exists to prevent. Also drops a literal NUL byte from the new re-probe module's signature separator, which made the file binary to git, and seeds `lastProbeAt` at registration so focus churn right after mount cannot defer the first re-probe indefinitely. Co-authored-by: Orca <help@stably.ai> * perf(windows): route relay host-capability probes through the async wsl/pwsh twins A paired web/mobile client resolves `useWindowsTerminalCapabilities` to a local target (TabBar's `isWebClient` gate, and `useSettingsNavigationMetadata` forces `{kind:'local'}`), so the new re-probe arms there too. But `window.api.wsl/pwsh` on a web client is not the ipc/app.ts channel — it is `host.wsl.*`/`host.pwsh.*` over the runtime RPC, which still ran the sync probes and blocked the desktop main event loop on `execFileSync('wsl.exe' | 'pwsh.exe')` for up to 5s per call. Switch those handlers and the relay preflight capability probe to the async twins added here; they share the same caches, dedupe and backoff, so remote callers see no behavior change. * fix(windows): harden async capability reprobes * fix(windows): dedupe PowerShell shell probes --------- Co-authored-by: Orca <help@stably.ai> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
4b5157b147 |
fix(codex): bound state DB recovery retries (#13109)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
2f30eb9af5 |
fix(ai-vault): block deletion of live sessions (#13108)
* fix(ai-vault): block deletion of live sessions * fix(ai-vault): retain external session authority --------- Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
2396e5e3e5 | fix(browser-pane): reschedule remote stream restart with bounded backoff (STA-3483) (#12787) | ||
|
|
f0443c326a |
fix(codex): recover interrupted state DB backfills (#12617)
* fix(codex): recover interrupted state DB backfills * fix(codex): detect mixed-case backfill timeout * fix(codex): harden backfill recovery review findings * fix(codex): keep process identity retries safe |
||
|
|
bc1e049b3f |
fix(terminal): defer metric option writes to unmeasurable panes (#12944)
* fix(terminal): defer metric option writes to unmeasurable panes Writing fontSize/fontFamily/fontWeight/lineHeight makes xterm re-measure cell size against the pane's current box. A hidden or mid-layout pane can measure a wrong-but-nonzero size, which latches (hasValidSize) and mis-keys the shared WebGL glyph atlas until a manual resize — the stuck variant of the P0 bold/blurry-font reports. Metric writes now land only on measurable panes; otherwise the latest values park per-pane and flush on the next safe fit or reveal (with a refit on the light tab-resume path, which otherwise skips fitting). Measurability helpers move to pane-fit-measurability.ts to stay under the pane-fit.ts line cap. * fix(terminal): key metric deferral by terminal, not pane view getPanes() returns a fresh toPublicPane() wrapper per call, so a WeakMap keyed on ManagedPane never matched across call sites: deferred metric options were dropped, not deferred. Key on pane.terminal, which is carried by reference and dies with the pane. Also from review: - flushDeferredPaneMetricOptionsIfMeasurable checks the pending WeakMap before the measurability probe, so the common no-deferral case costs zero forced style/layout on every reveal. - applyTerminalAppearance skips the apply (and the probe) when all five values are already live and nothing is parked; any settings write re-runs the pass over every mounted pane, and arming a no-op deferral would trigger a refit on the next reveal. - fitRevealedPane flushes first: its pixel/grid checks can both no-op and return without fitting, stranding parked options. - Font zoom folds its direct fontSize write into any pending deferral so the flush inside safeFit cannot clobber the user's zoom. Corrects comments that asserted a cell-size re-measure mechanism xterm does not have: CharSizeService measures via OffscreenCanvas TextMetrics, independent of the pane box, and only fontSize/fontFamily re-measure. Test fixtures now allocate a fresh pane view per getPanes() call, which is what production does and what hid the keying bug. * fix(terminal): re-check the fit floor after a metric flush performSafeFit evaluated the min cols/rows gate with the pre-flush cell size, then flushed and fit unconditionally. A large font jump on a narrow pane passes the gate at the old size and lands under it at the new one, so fit() pinned the PTY to the tiny grid the floor exists to reject. Re-check after a flush that actually landed. The parked values still apply, so the pane is never stuck on stale metrics; only the fit is skipped. * fix(terminal): route a reveal metric flush through the stable fit fitRevealedPane's new flush branch called safeFit directly, which is exactly what the function's contract forbids on reveal: resumeRendering has just re-attached WebGL, whose cell metrics transiently differ from the DOM renderer's, so a raw fit can propose a one-column-off grid and reflow — and xterm's wrap/unwrap is not a perfect inverse, leaving a diff-painting inline TUI corrupted. A landed flush leaves pixels unchanged with a diverged grid, the same shape as a snapshot resize, so it takes the same steady-grid repair. A real resize still fits synchronously, after the flush. Reachable via window wake, which calls fitAllRevealedPanes with no pre-flush loop. * fix(terminal): gate metric writes on the pixel box, not the fit floor canApplyPaneMetricOptions reused canMeasurePaneForFit, whose >=8 cols / >=4 rows floor exists to stop a fit pinning the PTY to a sliver. But the divider clamp is 50px, which clears the 48px pixel floor and proposes ~5 cols — so a pane dragged to the clamp deferred every font change and never flushed: it never hides, and its box never changes, so no reveal and no ResizeObserver entry ever arrives. It rendered a stale font until widened, where pre-PR the write was unconditional. Gate metric writes on display plus the pixel box only. Hidden panes and the transient worktree-switch overlay are near-zero, so they still defer — the deferral's purpose is unchanged. The cols/rows floor stays on the fit, including the post-flush re-check in performSafeFit. Apply and flush share the same predicate, so no "applies but never flushes" state can open up. * fix(terminal): flush heavy reveal metrics after WebGL resume |
||
|
|
ddf58d6d6a |
fix(terminal): restore preserved remote PTYs after host relaunch (#12990)
* fix(terminal): foreground preserved daemon PTYs * fix(terminal): keep snapshot sequence domains distinct * test(terminal): use active reconnect control * test(terminal): await reconnect control activation * test(terminal): validate reconnect with fresh control * test(terminal): tighten host restart evidence * fix(terminal): retry preserved PTY attach after inventory * fix(terminal): retry attach after overlapping inventory --------- Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
f057cbc85f |
fix(serve): recognize CLI-form serve args on the Electron process (#12818)
* fix(serve): recognize CLI-form serve args on the Electron process When the binary is launched as `… serve --port …` without the CLI rewrite that injects `--serve`, normalize argv so isServeMode, headless GPU flags, and serve option parsing all engage. Preserves existing `--serve*` flag behavior for the CLI-spawned path. Fixes #12677 * fix(serve): treat only CLI subcommand position as serve Parse bare `serve` as the first positional token after flags/values so an option value named `serve` cannot enable headless mode. Addresses CodeRabbit on #12818. * fix(serve): keep CLI redirects ahead of the serve argv rewrite Rewriting argv before maybeRedirectAppImageCliLaunch replaced the `serve` positional with `--serve`, so the redirect's command-name lookup saw a port number and bailed — dropping AppImage serve launches out of the CLI path. Also translate `--port=6768` (the CLI accepts it, getServeOptions only reads the next token) and the mixed `--serve --port` form, so a security-shaped flag like `--no-pairing` can no longer read as accepted while pairing stays on. Map lookups replace `in` on object literals, which turned a stray `serve toString` positional into a function spliced onto argv. * fix(serve): close the CLI-form serve gaps found in review second-instance: shouldActivateDesktopForSecondInstance matched only `--serve`, so a duplicate `<binary> serve --port …` — the ExecStart shape documented in docs/reference/headless-linux-server.md — promoted the live headless server to a desktop window, un-fixing #11935 on exactly the launch shape this PR legitimizes. findServeSubcommandIndex consumed a flag's value unconditionally while the rewrite consumed it only when the next token was not flag-shaped. The two could disagree and swallow the `serve` token, leaving `--serve` uninjected: #12677 again in a new shape (`--port --port serve`, `--port -- serve`). Both scans now share one definition of value consumption. `<binary> serve --help` / `serve help` bound a network-exposed runtime server with pairing on and printed nothing; the AppImage redirect already routes those three tokens to the CLI, so refuse them here too. `--no-pairing=false` translated to `--serve-no-pairing` with the value dropped, disabling pairing for an operator who asked for the opposite. The CLI reads its serve booleans as `flags.get(name) === true`, so a boolean is now translated only in its bare form and the `=` form rides through as the CLI treats it. Tests: spec-derived parity between src/cli/specs/serve.ts and the rewrite, covering both ends of the contract (serveOrcaApp and getServeOptions); a source-text lock on the index.ts redirect/rewrite ordering, which reverted silently green before; an exhaustive self-consistency property test; and the real GUI launch argv shapes that must never enter serve mode. --------- Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
c9485fdded |
fix(computer): fence macOS HID coordinate clicks (#12981)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
87d768058c |
fix(terminal): recover stale persisted owner bindings (#12976)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
f2d62a7887 |
fix(i18n): localize the status bar Resource Manager tooltip and remote-host count (#12478)
* fix(i18n): localize the status bar Resource Manager tooltip and remote-host count The Resource Manager tooltip/aria label and the SSH segment's host count were assembled from bare English literals inside helper functions, so they stayed English under every non-English UI language while the labels around them translated. Route them through the catalog with _one/_other plural keys and whole-line messages (locales reorder and repunctuate the summary), and add en/es/ja/ko/zh entries. Root cause of the miss: audit-localization-coverage bailed on any ancestor binary expression whose operator was not `+`, which hid every string under a `cond && <JSX/>` guard or a `?? 'fallback'` — including this segment's 'Connecting…'. Only comparison operands are code, so keep `??`, `||` and `&&` walking, and localize the four real strings that surfaced. Co-authored-by: Orca <help@stably.ai> * fix(status-bar): flag the space-scan tooltip row instead of matching its English text The tooltip tinted a row with `line === 'Space scan ready'`, so routing that copy through the catalog silently dropped the tint in every translated build. Return `{ text, emphasized }` and let the segment read the flag. Adopted from #12439 by @smwbev. Co-authored-by: Evgenii <smwbev@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai> * fix(status-bar): key Resource Manager tooltip rows by role instead of array index Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
8ddf575fe6 |
Revert "Remove source control group order preference (#12785)" (#12955)
This reverts commit
|
||
|
|
a7ffb244e4 |
perf(terminal): bound the reattach payload agent-signal scan (#12681)
hasCursorAgentReattachPayloadScreenSignal built a char-by-char copy of the entire reattach payload so it could read the last header plus 5000 chars. On a 2MB daemon snapshot that cost 17.5ms of synchronous renderer main-thread work — ~75% of what xterm then spends parsing the same bytes — and the miss case paid it in full for a result that is always false. Two changes, both matching existing in-tree precedent: bound the scan to a 256KB tail (as the kitty tracker already bounds its own scan), and strip via the shared precompiled CSI_SEQUENCE_PATTERN instead of a hand-rolled loop, which is also faster in V8 because it copies spans rather than building a rope per character. 2MB snapshot, header hit 17.5ms -> 0.80ms (22x) 2MB snapshot, miss 8.7ms -> 0.52ms (17x) 200KB snapshot, header hit 1.5ms -> 0.62ms (2.4x) config/scripts/terminal-reattach-payload-scan-benchmark.mjs reproduces this and asserts every candidate agrees with the baseline before timing it. It also records a negative result: porting the daemon mouse mirror's includes() pre-filter to the kitty tracker makes reattach slower, because snapshots always contain the introducer. Adds guards for the two behaviours a future shortcut would silently break: a CSI-split header must still match, and a header behind the tail bound must not. Also byte-pins POST_REPLAY_REATTACH_RESET_KEEP_MOUSE, which shipped unpinned. Co-authored-by: Orca <help@stably.ai> |
||
|
|
c2da0e47f9 |
fix(computer): deliver macOS coordinate clicks via the HID event tap (STA-3433) (#12839)
Mouse events posted with CGEventPostToPid reach the target app with no window association, so AppKit never routes the press to a view: hover states fire but the control is never activated, and the mouseUp is dropped outright when posted back-to-back. Post click events to the HID event tap instead (as keyboard synthesis already does), pace them, and stamp mouseEventClickState so multi-clicks register. Synthetic clicks now also report verification unverified/synthetic_input from the helper itself, matching the other synthetic actions. |
||
|
|
b0ba51831c | Add per-worker model and effort overrides (#12851) | ||
|
|
7da9368b78 |
fix(terminal): fence detached daemon endpoint ownership (#12709)
* fix(terminal): fence daemon endpoint ownership * fix(terminal): clean failed daemon PID claims * fix(terminal): close daemon ownership review gaps * test(daemon): release startup IPC in boot smoke * test(daemon): mirror production stdio in boot smoke * fix(daemon): exit after rpc shutdown cleanup * fix(terminal): make the socket name the daemon endpoint authority The reported failure was a live daemon hosting PTYs that nothing could reach: terminals acknowledged input and never ran it, listings diverged from reality, and restarting the app never helped because the detached helper survived. The ownership fence added for it could not fire in the sequence that produces the split brain. libuv unlinks the pathname a server bound to when that server closes, with no ownership check. A daemon that lost its endpoint name therefore deleted whichever socket then sat at that path — including a live replacement's — stranding a daemon that still hosted every session. Bind a private same-directory name and hard-link it into place instead: libuv can only ever unlink our own bind name, the exclusive link is a kernel-enforced endpoint claim, and the canonical name is removed only under an inode ownership check. The bind name replaces the basename rather than extending it, so it cannot overflow sun_path. killStaleDaemon removed the PID record unconditionally immediately before every fork, so the exclusive PID claim was always uncontested at bind time. It also unlinked a live daemon's endpoint whenever a connect probe merely timed out, and treated a `ps` timeout as proof of PID recycling. Now only positive evidence of a dead endpoint authorizes reclaiming it, SIGKILL is confirmed rather than assumed, and a daemon that cannot be proven stopped keeps its record and endpoint while the launcher refuses to fork beside it. A daemon whose endpoint was taken over now retires itself, draining rather than killing, so an unreachable orphan stops being permanent. A repaired PID record re-derives entryPath, appVersion and the Linux incarnation markers from the authenticated owner instead of dropping them; without appVersion a healthy daemon read as a permanently stale bundle and, on Windows, went unpinned against daemon-host pruning. Repair failure now fails open — abandoning a healthy daemon over a pid file write cost every persistent terminal on the machine. Also: treat only ENOENT as an unclaimed record so a Windows file lock is not reported as an ownership conflict; settle start() before close() so an accepted connection cannot defer it forever; sweep abandoned claim and bind names; and type the endpoint-identity seam so a rename cannot silently disable the fence. Adds a real-process handover smoke that reproduces the failure with two daemons racing one endpoint, and wires it into the native-smoke job. * fix(daemon): retire only on proven endpoint ownership loss The ownership watchdog read a null identity for any stat failure, so a transient EACCES or EIO on the runtime directory would retire a daemon that was still serving every terminal on the machine. Distinguish "the entry is gone" from "the probe failed" and act only on the former. Also require the loss to persist across two polls: a replacement publishes by unlink-then-link, and a single observation can land in that gap. * fix(daemon): source repaired ownership metadata from the authenticated hello Adversarial review found three defects in the previous two commits. Re-deriving entryPath from the owner's command line truncated it at the first space. A command line is a single space-joined string, so `C:\Program Files\Orca\...` and `/Applications/Orca 2.app/...` came back as `"C:\Program` and `/Applications/Orca`. getDaemonLaunchIdentity treats a present entryPath as authoritative, so a healthy daemon read as `different_app_path` and was killed and re-forked — worse than the missing-metadata case the derivation was added to fix. Carry entryPath and appVersion as optional fields on the daemon hello identity instead: the daemon already has both from its own argv, and per docs/reference/remote-wire-compatibility.md a new optional field is safe because every reader falls back when it is absent. This also removes a synchronous `ps` spawn from the Electron main thread during startup. `start()` rolled back the PID record even when it never published one. Losing the endpoint link now runs that path, and the ownership-checked unlink briefly renames the incumbent's record aside — enough to strand a live daemon's ownership. Roll back only what we actually wrote. publishDaemonSocketPath read its identity from the canonical name after linking, so a concurrent unlink returned null: no ownership watchdog and no endpoint cleanup on any shutdown path. Read it from the bound name before linking, which shares the inode. Refusing to fork beside an unconfirmed daemon left the user with no daemon at all and no in-app recovery, since restart re-entered the same fence. We have just proved something answers the endpoint, so adopt it in degraded mode: live sessions keep working, fresh terminals run locally. SIGTERM is also individually guarded now — an EPERM fell into the blanket catch and reported "nothing alive", authorizing the very duplicate this fence exists to prevent. Also reset the ownership-loss streak on an inconclusive probe so the confirmations are consecutive, and sweep scratch names before the launch so a failed launch still reclaims them. |
||
|
|
ae1ed5e886 |
Remove source control group order preference (#12785)
* Reorder source control to show staged changes first by default Stages are closest to the commit action and most relevant to the commit workflow. Merges untracked files into Changes visually while preserving their Git area. Removes the untracked-first preset and includes migration logic for existing user settings. * Drop source control group order user preference Remove the sourceControlGroupOrder setting and related UI, migrations, and persistence logic. The source control view now always displays sections in the order: staged changes, unstaged changes, untracked files. * Reorder source control to show changes before staged Aligns with the edit-stage-commit workflow by showing unstaged changes (active edits) before staged changes (queued for commit). |
||
|
|
74ac7049ec |
fix(windows): make managed grok-hook.cmd safe when GROK_HOME is unset (#11782)
* fix(windows): make managed grok-hook.cmd safe when GROK_HOME is unset Fixes #9358 and #9941. cmd.exe expands %VAR:~n,m% at parse time. When GROK_HOME is unset (default outside Orca terminals), the generated length/trailing-backslash guards became a syntax error and every Grok hook event failed with exit 255. - Skip substring work when GROK_HOME is undefined (if defined + goto) - Replace if "%x:~-1%"=="\" (itself a quote-parser bug) with findstr - Extract Windows script builder; add template + spawn tests * fix(windows): harden grok-hook GROK_HOME guards and tests Address review on #11782: - Inject grokHome via buildWindowsAgentHookPostCommand extra form lines (no fragile string replace of the shared payload line) - Spawn tests delete GROK_HOME and keep PORT/TOKEN/PANE_KEY set so the GROK_HOME path actually runs before curl * fix(windows): cover Grok hook home boundaries --------- Co-authored-by: OrcaWin <alpha-eng@stably.ai> |
||
|
|
4c49989c2e |
refactor(codex): delete the unreachable managed shared-mirror lane (#12614)
PR 9501 shipped real-home routing for the host system default, and the env override that could turn it back off was never a shipped control. The managed-account half of the shared runtime mirror has been unreachable since: every host account routes to its own self-contained CODEX_HOME before that code runs. Delete the flag module and its env plumbing plus the managed branch of syncForCurrentSelection and the six helpers only it called. The three lanes that still use the shared mirror -- Windows, a custom CODEX_HOME, and a hook-lane gate that reports unusable -- are untouched, as are every legacy migration and the WSL read-back helpers. |
||
|
|
38ba22ecd1 |
fix(browser): align cookie import safeguards (#12607)
* fix(browser): align cookie import safeguards * fix(browser): preserve sessions on failed cookie imports * fix(browser): bound single-label cookie replacement * fix(browser): preserve host-only parent cookies * fix(build): bundle cookie scope parser |
||
|
|
fde816e4ee | move folders (#12758) | ||
|
|
d72daf8153 |
fix(release): admit PTY consumer in SSH watcher isolation gate (#12754)
#12746 only delivers pty.data after an authenticated openClient grant. Update the macOS release harness to use a credentialed --connect client and open a legacy session-owner so the gate still exercises watcher isolation without timing out on the initial PTY echo. |
||
|
|
d15939c5fd |
fix(terminal): recover rejected paired-runtime input (STA-2830) (#12675)
With a desktop client paired to a remote Orca runtime, terminal panes could report connected, writable, and `terminal.send` returning accepted — yet keystrokes never reached the agent. No error, no banner, no recovery; input silently vanished. The ticket was really two bugs. The attach half was already fixed by #12589 (subscriber-driven daemon attach), confirmed by reproducing against current main. This fixes the remaining half: a write the host refuses had no way to tell anyone. A capability-negotiated `WriteUnavailable` opcode carries that refusal back to the client, where it feeds the pane's pre-existing recovery hook. Capability gating matters because decoders reject unknown opcodes on desktop — and, worse, silently drop them on mobile — so the signal is negotiated in the subscribe handshake. Verified per direction: an old host strips the unknown Subscribe key, an old client omits it so the host never emits, and capability cannot be inherited across resubscribe. Independent review then found the signal was being delivered and discarded: recovery demanded an authoritative liveness answer, and `pty:hasPty` had no `remote:` guard, so a paired pane's id fell through to the LOCAL provider, which returned false, and recovery bailed before remounting. Every test stopped at the transport boundary, so all of them passed while the pane stayed just as stuck. `pty:kill` already had exactly that guard. The fix makes main answer LESS rather than claim more: `pty:hasPty` now returns unknown for a `remote:` id instead of a fabricated false, because main cannot speak for another host's PTY. The remount is then authorized by positive evidence — the process that owns the PTY stating it refused this specific write over a live negotiated connection — not by inference from silence. Local and app-SSH ids keep the probe, where a false genuinely means the shell died. Nothing is destroyed on this path; the remount rebuilds the renderer over the session it already had. An end-to-end test now carries a rejected write from the host through to an actual remount, which no prior test did. A surviving mutant was also killed: the legacy-binary capability gate could previously be deleted with nothing turning red. The reliability gate stays experimental — live paired journeys and mixed installed-release evidence remain uncollected. Fixes STA-2830. |
||
|
|
06780260c0 |
test(remote-runtime): run an old client and an old server against current code (#12682)
Mixed versions are the normal state of the remote-server feature: users update clients and servers independently. Until now nothing tested that. Every cross-version claim was made by code reading plus unit tests with hand-written old/new shapes — enough to catch design problems, not enough to catch a real skew regression. This runs the REAL protocol implementations from two builds against each other in one process: the actual host methods and RPC dispatcher on one side, the actual renderer multiplexer on the other, with a transport that reproduces the production asymmetry — each side decodes with its OWN codec and drops frames whose opcode it does not know. A frame survives only if the RECEIVING build understands it, which is what makes this level sufficient without launching two apps. The old side is a genuine checkout extracted from the release tag; the extracted client was confirmed to lack a symbol that exists only on main. Journey: subscribe, first snapshot, input reaching the process, live output, hide/reveal snapshot, transport drop, resubscribe, input landing again — across old->new, new->old, and a current/current control. Every step ends on an observed-state barrier; no sleeps. The oracle asserts the recorded step list, the exact 16-frame named sequence, negotiated capabilities, the exact input the host wrote to the PTY, rendered content, and zero decoder-rejected frames. A host method the stub lacks is recorded by name and asserted empty, so a harness gap cannot masquerade as a wire break. Detection is proven per violation shape, and it attributes each to the correct side: an unnegotiated opcode goes red only where a decoder would reject it, a removed published field goes red only where an old client consumes it, and a legal additive field stays green in all three pairings so the harness will not cry wolf on safe changes. It also documents the three compatibility rules in docs/reference/remote-wire-compatibility.md, linked from AGENTS.md, since they previously existed only as folklore — notably that "decoders reject unknown opcodes" is true for the desktop decoder but NOT for mobile, which silently drops them. Deliberately scoped: terminal stream only. The session-tab sync channel is not covered, nor agent-session publications, file/Git RPCs, mobile E2EE framing, or the relay transport. Two version points, so a regression introduced and reverted between them is invisible. CI selection was verified rather than assumed — `vitest list` confirms 0 matches under the shard's exclude and 4 under the dedicated job — because a lane silently running zero tests is precisely how a host-side defect escaped CI earlier in this series. Closes STA-3469. |
||
|
|
0f9caf52b1 |
fix(ssh): time out stalled remote file streams (#11364)
* fix(ssh): time out stalled remote file streams * test(ssh): cover stream dispose-listener cleanup * fix(ssh): pause file stream deadlines during sleep * fix(ssh): replay suspended state to late streams * fix(ssh): allow slower file stream progress --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
84df99e2f1 |
test(serve): pin zero duplicate agent resumes across headless serve desktop promotion (#12666)
STA-1716 reported that a packaged `orca serve` could become the single-instance owner after the desktop app exits, leaving Dock/Finder unable to restore a window — and that forcing a reopen made the headless process hydrate a renderer that interrupted and DUPLICATED live agent sessions. Verification against main found every criterion already fixed (#8646 for desktop promotion and the fail-closed CLI, #12212 for duplicate serve activation, #12574 + #9729 for the resume/ownership guards). The genuine gap was criterion 6: the ticket's own automated regression never existed. An existing reliability gate asserted PTY identity survives promotion, but nothing asserted what the incident was actually about — how many agents the promoted renderer resumes. This adds that coverage: a unit/service-level journey that drives the real single-instance lock, activation gate, settle and focus paths, then runs the real resume logic against a store seeded as a renderer freshly mounted inside the serve process, asserting zero duplicate resumes. `settleServeDesktopActivation` moved from `index.ts` into its own module with identical semantics, so the test drives the real decision rather than re-implementing it — the earlier repro had to mirror that logic locally, which is the "test passes without running the scenario" failure mode. Proven to be a real oracle: breaking each guard individually turns it red, and reverting the pre-#12574 pane form reproduces the incident exactly (two duplicate `codex resume` tabs). |
||
|
|
cbc005c8aa |
fix(remote-runtime): materialize the host surface when reconnecting a terminal pane (#11542)
Reconnect could never recover a terminal pane whose host-side process was gone (host restarted, or the workspace was never opened there): recovery only polled the tab inventory, which can never create the surface it is waiting for, so Reconnect spun for ~60s and gave up permanently. Verified with a deterministic reproduction: on main the recovery path issues 51 inventory polls and zero activations across both an automatic online trigger and a manual Reconnect click; with this change the pane re-materializes, rebinds and accepts input. Review found and fixed three further defects beyond the original change: - an activation answered with a stale ready handle left the loop polling forever instead of re-activating; - a non-missing activation failure (e.g. an older host without the method) never fell back to inventory; - host-side, activating a parked surface permanently deleted the host tab, because an already-absent persisted binding was read as a competing owner *after* the destructive retirement had already run. Independent review confirmed by mutation testing that every production change is covered by a test that fails when it is reverted, that only an authoritative inventory can retire a pane, that the loop is bounded under every failure mode, and that the unknown-liveness guard (proven death required before retirement) is intact. Fixes STA-3002. |
||
|
|
9507cbce0c | fix(terminal): rate-cap WebGL atlas recovery (#12622) | ||
|
|
69ca9f91b3 |
fix(status-bar): invalidate the CLI session count on kill and restart (#12468)
* fix(status-bar): invalidate the CLI session count on kill and restart `pty:management:killOne` / `killAll` / `restart` tear sessions down via `adapter.shutdown()` and broadcast nothing — unlike `pty:kill`, which ends in `sendPtyExitToRenderer`. The status-bar count is an event-sourced cache, so killing sessions from Manage Sessions or "Kill all terminals" left the `>_ N` chip frozen until the popover was opened, which itself triggers a refresh. > [!NOTE] > The dual-source split described in the issue text was already fixed by merged #9387. This closes a *different* remaining invalidation gap that produces the same reported symptom. Broadcast the teardown so the chip updates without needing the popover opened. Fixes #8372 Co-authored-by: Orca <help@stably.ai> * test(e2e): add recordable proof for status-bar-cli-session-count Fails on origin/main, passes on this branch. Test: drops after Manage Sessions kills a foreign daemon session, popover never opened Co-authored-by: Orca <help@stably.ai> * fix(status-bar): avoid duplicate inventory refresh after kill all --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
fb27702100 |
feat(updater): restart hourly build numbers per version, restyle the timestamp (#12587)
The number answers "which build of 1.4.163 is this", so carrying it across versions made it meaningless — 1.4.164 opened at 38 for no reason a reader could see. It now counts titles matching the base version being built, so a version bump restarts the series at 01. Deriving it moves from workflow jq into the script, because the number depends on the base version and only the script knows which base the published tags resolved to. Timestamps go from `07-31 13:54` to `Jul 31, 1:54PM`, still Pacific. Co-authored-by: Orca <help@stably.ai> |
||
|
|
847c8c852d |
fix(agent-status): correlate manual Claude compact hooks (#12332)
Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
4734428654 |
fix(i18n): stop the repair policy de-localizing CJK UI labels
The build-time repair layer carried overrides that rewrite already-correct CJK values back to English. Most are inert against today's catalogs but fire on the next regeneration, so they read as latent regressions rather than policy: - zh workspace status picker (Play/Flag/Zinc/Rose/Emerald/Amber/Violet/Sky/ Blue/Neutral) and `sheet`/`page` were pinned to English while every sibling option, and ko/ja/es, stay translated — half a Chinese picker. - The zh `蓝色的`/`琥珀色`/`中性的` phrase fixes correctly flagged the adjectival 的 form but replaced it with English instead of the bare color noun. - ja `Play` was pinned to English though the catalog already reads 再生. - A value-wide zh `Open: '进行中'` mapped every "Open" to "in progress", including the button that opens an MCP config file. "Open" is a verb (打开) on buttons and a state (开放) beside 已关闭, so no single mapping fits. Catalog corrections in the same area: - The GitHub/PR state picker key override read 진행 중 / 进行中 for ko and zh while ja already had the correct オープン; now 열림 / 开放, matching 닫힘 / 已关闭 on the sibling entry. - The terminal cursor-color group is the on-screen cursor, not the Cursor editor; ja already had カーソル, ko/zh now get 커서/光标 instead of "Cursor". - Tailwind swatch labels 天空 (the sky) and 锌 (the metal) do not read as colors; now 天蓝/锌灰, and ja 空 becomes 空色. - The ko disk-usage heading was pinned to bare "Space" while its own description says 저장 공간; both now use 저장 공간. Two policy tests pinned the Play de-localization. They diagnosed the input correctly — 玩 / 遊ぶ are wrong for a play icon — so the expectations move to 播放 / 再生 rather than English. Destructive drift (localized -> English on regeneration) drops from 40 to 21 for zh, 6 to 5 for ja, and 5 to 3 for ko. What remains is deliberate: search qualifiers, path and filename literals, and product names. |
||
|
|
f9a5920954 |
fix(i18n): stop repairCatalog crashing on un-bootstrapped locale keys (#11728)
en.json carries ~190 keys per locale that the locale catalogs have not been bootstrapped with yet, so every repair-locale-catalog run threw a TypeError before doing any work. Skip missing leaves instead. Split out of #11728 so the crash fix can land without the catalog regeneration, which still needs native-speaker review. Co-authored-by: Turtle-Hwan <turtlehwan@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
aa001101f2 |
fix(i18n): repair the stale SshTargetForm ko override
The override for SshTargetForm.137e88ce8d held a truncated relay-TTL sentence ending mid-clause at "최대:", which renders "Timeout after disconnect (seconds)" — the sibling key 55c56cf2c7 — not its own English source. #12192 only corrected the terminals token inside that wrong sentence. Point it at the value ko.json already ships, so a catalog repair cannot overwrite the correct string with the wrong one. |
||
|
|
a202d92772 |
fix(localization): stop the repair policy rewriting translated generic terms to English (#12192)
Fixes #12113. shouldPreserveEnglishValue keyed on the English value, so any key whose source string equalled a NEVER_TRANSLATE_VALUES entry was forced back to English on every repair run — agent, commit, repo, terminal and Continue were all on that list. Measured on a clean checkout: ko 279, ja 461, zh 1180, es 479 values rewritten, the large majority destroying translator work. 17 generic terms move into locale-generic-ui-terms.mjs, and the brand revert now skips a term's canonical rendering, so genuinely nonsensical forms (zh 回购, ja/zh 端子, es Comprometerse) still fire while 터미널/커밋/エージェント survive. Brand, path, and code tokens are untouched — MD -> 医学博士 and HEAD -> CABEZA are why that list still earns its keep. No catalog values change; every file is under config/scripts/. Co-authored-by: AnddyAgudelo <44873492+AnddyAgudelo@users.noreply.github.com> |
||
|
|
705e17a2a0 |
fix(i18n): correct semantic errors in Korean UI copy (#11169)
Nine ko values said something other than the English source. The riskiest is
SourceControl.6d7f2a47e5 "Discard folder", rendered as 폴더 삭제 ("delete
folder") next to a sibling delete-untracked action. Others: "Only branches Orca
named itself" read as "branches named Orca"; "staged changes" as 단계적
("phased"); "first-party cloud" as the mojibake 1方클라우드; "discard the
deletion" as "the deletion is deleted"; "Stage all changes" as a sentence
meaning "prepare"; and Recipes as 조리법 (cooking recipes).
EphemeralVmsPane.skillTitle is dropped from the PR's test and override — the
key was renamed to cloudVmSkillTitle on main, so the assertion would resolve to
undefined.
Co-authored-by: ShinSungkyu <kxu4583@naver.com>
|
||
|
|
5adc5d06c8 |
fix(i18n): pin the Orca Mobile "New" badge override and correct ja
The badge value is pinned in locale-key-overrides.mjs, so the ko/zh fix from #10664 would have been reverted by the next catalog repair. ja carried the same defect — 新規 reads as "create new" — and is corrected alongside. |
||
|
|
eed74724ac |
fix(i18n): localize automation contextual tour (#12270)
The shared Automation tour copy was rendered without passing through translate(), and the overlay surface hardcoded its default Next and Done labels. Copy is keyed off the step id rather than its position, so inserting a step ahead of them cannot shift the text onto the wrong step. Co-authored-by: 5Hyeons <ohs2251@naver.com> |
||
|
|
2548b816c0 |
Keep the app responsive when security software slows process creation (#12217)
* fix(ports): keep the app responsive when security software slows process creation Orca ran the workspace port scan's probe commands (lsof/ps on macOS, netstat + powershell.exe on Windows) directly in the Electron main process. libuv performs process creation inline on the calling event loop, which in the main process is the browser UI thread, so an endpoint-security module hooking CreateProcessW froze the whole window for the length of the spawn. The same stall also produced a false diagnosis: the 4s command watchdog was armed before execFile (local-workspace-port-scanner.ts:389 -> :410), so its deadline had already passed by the time the command started. Every scan on a hooked host reported a command timeout, tripping the 60s -> 5min backoff and the "Port scanning is temporarily paused after a command timeout" banner even though the commands themselves were healthy. Probe commands now run on a lazily created, unref'd worker thread with FIFO one-at-a-time dispatch, and the watchdog is armed after execFile returns so it measures the command rather than the spawn. Node's own execFile timeout kill (killed: true) is classified as a command timeout, keeping the backoff working for genuine hangs. A scan that observes a stalled spawn skips its optional metadata commands for that cycle, capping a hooked-host scan at roughly one stall instead of three. Closes #11161 * fix(ports): keep advertised URLs when a stalled spawn skips port metadata Review follow-up on #11161. The stalled-spawn early return handed scanWorkspacePorts raw ports with no cwd/commandLine, so every port failed attribution and reconcileAdvertisedUrls told the watcher each worktree's listeners had vanished. shouldEvictAfterScan then deleted every cached advertised URL and broadcast a removal event; those URLs are only ever captured from live PTY output, so the dev-server link was gone until the server restarted. The scanners now report metadataAvailable, and reconciliation is skipped for a scan that never gathered attribution evidence. The skip is also no longer self-perpetuating: on an EDR-hooked host every spawn stalls, so gating purely on the current scan's spawnMs made every port permanently external (Stop refused with 'Only workspace-owned local processes can be stopped here.'). Metadata is now re-probed on the scan after a skip, matching what the comment and test name already claimed. Co-authored-by: Orca <help@stably.ai> * test(windows): stop a temp-dir lock from failing the CLI launcher smoke test The native launcher assertions passed on windows-latest, but teardown's rmSync raced Windows' release of the image handle on the exe the test had just executed and threw EPERM, failing the job. Cleanup now retries and, on Windows only, tolerates a residual lock code instead of reporting it as a launcher regression. Co-authored-by: Orca <help@stably.ai> * fix(ports): scope the metadata skip away from attribution-dependent scans The metadata skip was a process-wide parity flag, so Stop and the localhost-label allowlist could land on a degraded cycle and reject a port the panel had just shown as workspace-owned. Give those callers an explicit requireMetadata option, and carry the previous cycle's listener metadata forward so a skipped background scan no longer republishes workspace ports as external. Also pin the watchdog ordering: the stall in the execution test was shorter than the watchdog budget, so a watchdog armed before execFile still passed. * build: guard worker-thread entries against electron imports (#11161) Electron's module is not registered on worker threads, so require("electron") throws "Cannot find module 'electron'" inside a main-process worker and kills it at startup (verified on Electron 43.1.0). plain-node-entry-guard covered only forked plain-Node entries, so the five worker entries relied on hand-written "must stay electron-free" comments. The port-scan probe worker is one import away from port-scan-command-client.ts, which deliberately contains require('electron'). A violation there fails closed at runtime while every unit test still passes, because the client's require is try/caught on the main thread. Covers stt-worker, warp-theme-parser-worker, session-scanner-opencode-sqlite-worker-entry, main-thread-hang-watchdog-entry and port-scan-command-worker-entry. The scan is transitive over the emitted chunk graph, so a shared chunk that reaches electron is caught too. Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> * test(windows): retry teardown for main's duplicate-PATH launcher fixture Main's new csc-compiled harness runs an exe from the temp tree, which is exactly the image-handle/AV lock the merged-in removeFixtureTree retry exists for; its bare rmSync would report a teardown lock as a launcher failure. Co-authored-by: Orca <help@stably.ai> * test(ports): pin the packaged-asar worker entry path resolveWorkerEntryPath's packaged branch never runs in dev or e2e, so the path construction had no coverage. Split the electron read out of it and unit-test both layouts. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
e8d4818b5a |
test(env): keep happy-dom MutationObserver callbacks alive across GC (#12438)
happy-dom holds each observer's internal callback in a WeakRef, so a GC pause under sharded CI load permanently kills a still-connected observer. That is what made the activity portal readiness test flake: once the observer died, no amount of re-applying ready DOM could release the latch. Co-authored-by: Orca <help@stably.ai> |
||
|
|
5bd2f59d29 |
fix(runtime): open files from sibling workspaces (#11369)
* feat(runtime): match files to workspace owners * fix(runtime): resolve terminal paths through sibling workspaces * fix(editor): route restored sibling workspace files * fix remote sibling file ownership routing * fix(editor): migrate restored sibling file owners * fix(editor): revalidate restored owner activation * docs(review): record PR 11369 correction evidence * fix(editor): reject collision before activation prep * docs(review): record PR 11369 final correction * fix(editor): retain projected reconciliation narrowing * chore(review): keep verification artifacts out of PR * fix(editor): harden restored owner migration * fix(runtime): resolve workspace root terminal paths --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
3d6d6dd67d |
fix(orchestration): scope agent lineage to its owning run (#11203)
Use durable Task-to-Run ownership and current pane, process-incarnation, and Run-generation authority for sidebar agent lineage. Add schema migrations, bounded lookup indexes, fail-closed renderer cleanup, and runtime/RPC regression coverage. Co-authored-by: Jaeyoung22 <89302528+Jaeyoung22@users.noreply.github.com> |
||
|
|
50594c55a9 |
Stop the Windows Orca CLI from crashing when the environment carries both PATH and Path (#12218)
* fix(windows): stop the Orca CLI dying on a duplicated PATH/Path environment The packaged Windows `orca.exe` launcher read `ProcessStartInfo.EnvironmentVariables`, whose lazy getter copies the case-sensitive process block into a case-insensitive dictionary via `.Add`. An inherited block carrying both `PATH` and `Path` threw `ArgumentException: Item has already been added. Key in dictionary: 'PATH'`, so every `orca` invocation exited 1 before Electron started (native/windows-cli-launcher/OrcaCliLauncher.cs:46, printed at :67). The launcher now mutates its own environment with `Environment.SetEnvironmentVariable` and never touches either `ProcessStartInfo` env property, so `CreateProcess` passes a NULL environment block and the child inherits the live one verbatim. Orca was also minting the duplicate itself. `applyTerminalAttributionEnv` read `baseEnv.PATH` and unconditionally wrote `baseEnv.PATH`, so a Windows PTY that inherited `Path` got a second spelling; which one the child resolved was non-deterministic. `createLaunchEnv` did the same and, because its read always missed on Windows, shipped Agent Teams terminals a `PATH` containing only the tmux shim dir. `resolvePathEnvKey` (extracted from the existing precedent in windows-environment-path.ts) now drives every PATH read and write in the PTY env pipeline, and attribution collapses Windows onto the single OS-resolved spelling. Off Windows the resolver always returns `PATH`, so POSIX behavior is unchanged and a case-sensitive POSIX `Path` variable is never touched. Closes #12046 * test(windows): track the launcher's own-environment marker The #12046 fix moved ORCA_WINDOWS_PACKAGED_CLI_LAUNCHER and ORCA_CLI_COMMAND off ProcessStartInfo.EnvironmentVariables, but this asset test still pinned the old dictionary writes and failed. Co-authored-by: Orca <help@stably.ai> * fix(windows): follow the host block's PATH spelling on sparse daemon env patches Resolving a path-less Windows env to `Path` handed the daemon's own `{...process.env, ...opts.env}` merge both spellings when the host block spelt `PATH`. Fall back to the host block's own key, and collapse again inside the daemon since that merge happens after attribution. Co-authored-by: Orca <help@stably.ai> * fix(windows): resolve the live PATH spelling by block order, not casing Win32 resolves a duplicated variable by taking the first case-insensitive match in the block, so `resolvePathEnvKey`'s hardcoded `Path`-first preference targeted the shadowed spelling on the reporter's own `["PATH","Path"]` block. Drop the attribution-side collapse with it: it deleted the other spelling's value, and deleting the live key promotes the shadowed one, so an env that stripped down to empty lost both. * chore: drop unrelated merge formatting --------- Co-authored-by: Orca <help@stably.ai> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
79d3c847bd |
fix(runtime): attribute destructive close requests (#12238)
Attribute destructive runtime and daemon close diagnostics to the requesting client and exact target, record outcomes only after completion, and add reliability-gated attribution regressions. |
||
|
|
49dc113a0f |
Fix terminal corruption after restored snapshot replay (#12363)
* fix(terminal): preserve restored snapshot fidelity * test(terminal): align legacy history handoff snapshot expectation * fix(terminal): keep legacy snapshot panes mounted * fix(terminal): refresh snapshot capability after startup * fix(terminal): refresh snapshot capability in degraded startup * fix(terminal): await snapshot provider authority |