mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
d64ccc71bd7daf7ff64fe182b9bf522b90293397
8180
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d64ccc71bd |
fix(terminal): break the ConPTY foreground livelock that froze a repainting TUI (#12463)
`holdForeground` and `coalesceForeground` each cancelled the other's fallback timer on the Windows ConPTY DEC 2026 (synchronized output) path. A continuously repainting TUI such as Codex therefore left the foreground latch stuck open, so every later chunk was held instead of coalesced and output never reached the visible pane until the tab was refreshed. Break the mutual cancellation and mirror the hidden path's scan on the foreground path, carrying a marker tail so a ConPTY-split DEC 2026 marker is still detected. Nothing was wrong with Flutter — it was simply a long-running command behind a repainting TUI. Fixes #8754 Co-authored-by: Orca <help@stably.ai> |
||
|
|
42fc5375e8 |
perf(terminal): gate the Korean input-source probe on its setting (#13181)
prefetchKoreanInputSource ran under a bare isMac check, so every macOS user paid for it. Each refresh shells out to `defaults export | plutil | plutil` — four processes in the main process — and input-source toggle keys pass force: true, so one Caps Lock press costs two probes. terminalKoreanWonToBackquote defaults to false. Threads the setting through KeyboardHandlersDeps and into the effect's dependencies, so enabling it mid-session still warms the cache before the first Backquote. Co-authored-by: Orca <help@stably.ai> |
||
|
|
24003936a5 |
feat(terminal): Korean Won (₩) → backquote key mapping for Korean keyboards (#13104)
* feat(terminal): map Korean Won (₩) key to backquote on macOS Korean keyboard users type markdown code fences and shell backquotes on the key that US layouts reserve for ` — 두벌식 and 세벌식 390 put ₩ there, 세벌식 최종 puts *, so there was no way to type a backquote without switching layouts. Add a Mac-only terminal setting, "Korean Won (₩) to Backquote (`)", that rewrites the plain backquote-position keystroke to backquote while a Korean input source is active. It sits in Terminal → Advanced, right below the existing JIS Yen (¥) to Backslash (\) mapping. The rewrite keys on the keystroke position alone — no character or layout-variant knowledge — and follows the live input source through the existing MacNativeTextInputSourceTracker, which refreshes on focus and keyboard activity (Caps Lock / 한영 input switches never blur the window). Modified chords and IME-composed events pass through untouched. Covered by resolver and input-source tracker unit tests; verified manually in the GUI. * docs(terminal): clarify Korean Won mapping scope and add docstrings State in the setting copy that the backquote rewrite applies only while a Korean input source is active, and add JSDoc to the Korean Won resolver exports (addresses CodeRabbit pre-merge docstring coverage and copy-clarity findings). |
||
|
|
25a8c517e1 |
test(ime): restore coverage the composition-ownership change removed (#13168)
* test(terminal): pin the recorded Korean commit-before-newline order (STA-3132) Recorded first-party on Windows 11 + Microsoft Korean (HKL 0412) against the defect-era v1.4.164 build, with bytes read on the far side of the PTY: the terminal received ea b0 80 0d, the syllable strictly before the CR. The capture did not reproduce the suspected deferred-newline inversion. That route needed a session end carrying dataPendingReconciliation, which plain compose-then-Enter cannot produce because the IME finalizes first and the newline is never held; back-to-back arms at 25/60/120 ms did not reach it either. The test therefore pins the ordering rather than discriminating a fix. Co-authored-by: Orca <help@stably.ai> * test(terminal): restore Hangul back-to-back flush coverage deleted with the composition layer #12278 fixed a Hangul syllable that was not flushed before the next composition began — the force-end path, and the one that leaves stale glyphs behind. Returning composition ownership to xterm deleted both that patch and its test, so nothing guarded the behavior any more. Replays the recorded back-to-back arms (25/60/120 ms, read as 가\r나 at the PTY) against a real xterm Terminal. It passes on main: stock xterm flushes the committed syllable natively, so the removal was safe rather than a silent regression. Co-authored-by: Orca <help@stably.ai> * test(mobile): pin accessory-byte ordering behind a Hangul commit Returning composition ownership to xterm deleted the accessory-input commit tests along with the hook they targeted, but the guarantee they protected is user-visible and still applies: an accessory-bar keystroke must not overtake the syllable being committed, and must be suppressed when that commit fails. Drives the current hook with an Android composing-region trace rather than reconstructing the deleted coordinator. Co-authored-by: Orca <help@stably.ai> * test(terminal): replay recorded IBus and fcitx5 Hangul traces offline Commits interleaved with ASCII (한abc글) are the Linux IME gesture users report on, and its failure modes are a lost syllable and a doubled one. That gesture was only covered by tests/e2e/terminal-linux-ime-native.spec.ts, which needs a Linux host running a real input framework. Fixtures are the recorded captures from the sealed linux-final evidence run, replayed against a real xterm Terminal: exact onData, exactly-once counts across five repetitions, and the PTY bytes the recorded run actually received. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
84e7ca5212 |
fix(browser): reject Chromium product versions in cookie-import UA (#12811)
* fix(browser): reject Chromium product versions in cookie-import UA Do not persist Chrome/1.x User-Agents when a fork (e.g. Arc) reports its product CFBundleShortVersionString. Only engine-scale majors (>=70) are advertised; otherwise fall back to Electron's default UA. Preserves Chrome-shaped UAs for real Chromium engine versions. Fixes #12726 * fix(browser): reject malformed Chromium version tokens in UA Validate every numeric component before advertising Chrome/… so values like 70.not-a-version fall back instead of polluting the UA. Also build macOS app paths with path.join per coding guidelines. * fix(browser): drop already-persisted Chrome/1.x UAs on session restore The version gate stops new fork imports from writing Chrome/1.x, but profiles imported before it keep the broken UA in browser-session-meta.json and replay it on every launch, so affected users stay blocked with no in-app recovery. Move the gate into browser-session-ua (the module that owns UA shape, and the one both callers can import without a cycle) and drop an unadvertisable persisted UA during restore so the profile falls back to Orca's own engine UA. --------- Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
982570648a | fix(wsl): refresh hook relay on PTY reattach (#13139) | ||
|
|
fc8441194c | fix(terminal): preserve remote pane output after host restart (#13158) | ||
|
|
f858c5a13a |
test(daemon): stop skipping killStaleDaemon checks on Windows (#13154)
Three guards in daemon-health.test.ts skipped tests that assert PID-record and start-time logic. The file's daemonTestSocketPath already returns a real named pipe on win32, so none of them needed a Unix socket — the skips cost coverage for nothing. Verified on a Windows host: the file goes from 29 passed / 4 skipped to 32 passed / 1 skipped, restoring Windows coverage of killStaleDaemon's ownership decisions, which #12882 changed. The fourth guard stays: that test spawns a real child and binds a filesystem socket path, which fails with listen EACCES on Windows. |
||
|
|
c991bb27d3 | Add account-backed artifact sharing (#13012) | ||
|
|
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> |
||
|
|
0d29497f82 |
Expand Cmd+J and interleave open tabs with worktrees on search (#13120)
* Expand Cmd+J palette and interleave tabs/worktrees on query
Increase palette dimensions (900x600) and remove redundant secondary
labels ("Terminal tab", "Mobile Emulator tab") that crowded rows. When
a typed query matches both open tabs and worktrees, use a soft-split
layout: leading section preview followed by trailing section floor so
neither primary is buried under ~50 rows. Trailing section no longer
truncates to hard cap when paired with a larger leading section.
* Add type-alias search and fix multi-primary palette ordering
Add searchable type aliases (e.g. "terminal tab", "mobile emulator") so users can find items by type without cluttering the row display. Refactor multi-primary palette layout into orderMultiPrimaryPaletteItems to prevent selection/render order drift, simplify selectableItems derivation, and track trailing hard-overflow count separately from scrollable rest.
* fix(cmd-j): pin multi-primary layout generic for mixed item types
Typecheck failed because the ternary lead/trail arrays inferred a
WorktreePaletteItem[] | OpenTabPaletteItem[] union that could not
satisfy layoutMultiPrimaryPaletteSections' single T parameter.
|
||
|
|
f968583e95 |
fix(remote): stop a reachable Orca server with a closed workspace window from reading Ready (#12477)
A remote Orca server whose workspace window is closed keeps answering status RPC, so Settings > Available Hosts showed "Ready" and the status bar showed "Connected" while every graph-backed operation failed. Adds the shared predicate `isRuntimeWorkspaceWindowClosed` (`graphStatus !== 'ready' && desktopWindowStatus === 'openable'`) and one host-health derivation with a new `workspace-window-closed` state, consumed by both surfaces. Hosts that omit `desktopWindowStatus` are unaffected, so the connected-host count and overall dot do not regress. Fixes #12350 Co-authored-by: gatsby74 <gatsby74@users.noreply.github.com> |
||
|
|
6ea99f6607 |
fix(runtime): isolate same-path folder workspace PTY identity (#12474)
Folder-project workspace ids (`repoId::/path::workspace:<uuid>`) were compared via the suffix-stripping `splitWorktreeIdForFilesystem`, so every workspace sharing one directory compared equal at ~35 runtime call sites — PTYs leaked between siblings and paired/mobile clients hung on "Loading terminal". Both identity helpers now use the suffix-preserving `splitWorktreeId`, `stopTerminalsForWorktree` routes through the shared helper, and `findResolvedWorktreeIdForPath` gains a `targetWorktreeId` tie-break. Co-authored-by: dgk-dev <dgk-dev@users.noreply.github.com> |
||
|
|
ce20a109da |
Persist the Linear issue list view and per-workspace filters (#12710)
* Persist the Linear issue list view and per-workspace filters
Layout, grouping, ordering, columns, and attribute filters survive a restart.
Facet ids are workspace-scoped, so filters are kept per Linear workspace and the
active filter is *derived* from the selected workspace rather than reset by an
effect on switch — no ordering race can apply workspace A's facets to B, and an
unresolved or cross-workspace selection reads as unfiltered without erasing
anything.
A single shared catalog backs the renderer state, `TaskResumeState`, and the
strict `ui.set` schema, so a new view option cannot leave paired web/mobile/relay
clients rejecting the whole payload. Persisted values are normalized as untrusted
input: a corrupt preference or a single bad workspace entry is dropped without
taking the rest of the resume state with it.
Deriving the filter also removed the guard that used to make three neighbouring
behaviours safe, so they are re-scoped here:
- The primary-team facet reset now fires only on an in-workspace team change.
A workspace switch also changes the primary team, and clearing there wiped the
filter that had just been restored for the workspace being switched *to*.
- The list-read force check no longer fires on the session's first read, so a
restored filter serves warm cache instead of forcing a network round trip
behind a blocking spinner on every cold start.
- The filter dropdown derives "no single workspace" from `workspaceId` alone.
With an unresolved workspace it previously rendered the statically populated
priority section, whose clicks now have nowhere to be stored.
* Harden Linear view persistence against the failures review surfaced
Five issues, each found by a reviewer and reproduced before fixing:
- The filter dropdown's prune effect only ran when the user opened the popover,
because the filter was always empty at startup. Restoration makes it run on
mount, where `availableTeams` may still be the issue-scraped fallback rather
than the real fetch. Metadata complete for a *partial* team set passes every
R12 guard, so it pruned facets belonging to teams it simply hadn't seen — and
the write persisted, deleting them permanently. Gated on `teamsSettled`.
- `canonicalize` dedupes but enforces none of the transport bounds; only the
throwing parser does. So `serialize` could emit a 101-label filter that the
strict `ui.set` schema rejects, which drops the WHOLE taskResumeState — github,
jira and linear query included — on every subsequent write, since the renderer
resends the merged object each time. Added `boundLinearIssueAttributeFilter`
and a round-trip test built from serializer output rather than a literal, which
is the only kind that can catch renderer/schema drift.
- `linearIssueView` now carries `.catch(undefined)`: value tolerance stops at the
top level, so any future instance of the above is a cosmetic reset of the view
instead of silent loss of every other resume field.
- A workspace switch forced an uncached list read in both directions. The switch
is a later observation, so the null-baseline fix didn't cover it; the cache is
already workspace-keyed, making the force pure cost.
- Recency for the 20-workspace cap came from object key order, which is wrong
twice: re-filtering an existing workspace left it at the head (first evicted,
though just used), and an array-index-like key enumerates first regardless of
insertion, so a write could evict the very entry it added. Recency is now an
explicit ordered key list.
Also adds the nested parity assertion — the top-level one compares only
TaskResumeState's own keys, so a field added to LinearIssueViewResumeState stayed
invisible to it, which is exactly what `.strict()` rejects.
The wiring test was blind: deleting the hydration guard outright left all four
assertions green. The gate is now `shouldPersistLinearIssueView`, unit-tested
directly, and the file is renamed to the repo's `*-boundary.test.ts` convention
with an assertion that fails on that mutation.
* Log discarded Linear views and fix empty-filter serialization
- Schema now logs when linearIssueView is discarded, making validation failures visible
- Fixed serialization: filters that become empty after bounding are now omitted
- Added AssertNoExtraKeys type check for bidirectional schema/type parity
- Refactored view option catalogs to use canonical constants, preventing UI/schema drift
* Remove workspace persistence limits and LRU eviction
Stop capping persisted Linear workspace filters at 20 and evicting
least-recently-used workspaces. Simplify persistence to store all
workspace filters, gate persistence only on resume state application,
and remove tests that pinned implementation details. Users can now
persist filters for all their workspaces without arbitrary limits.
* add test for linear persistence
* Improve Linear filter test clarity and fix e2e overlay dismissal for CI
- Convert parameterized filter-pruning test to sequential assertions
- Fix dismissOverlayChrome to toggle overlay triggers instead of
force-clicking inert page elements in headless CI
* Prevent TaskPage from stealing Escape from Radix menus
- Add check to detect open Radix dropdown menus and popovers; return
early from Escape handler to respect their capture-phase ownership
- Update overlay dismissal in e2e tests to use keyboard.press('Escape'),
now that TaskPage no longer interferes
* The capture-phase Escape guard in TaskPage bailed out for open dropdown menus and popovers, but an open Radix Select matches none of those selectors: the shared SelectContent wrapper (src/renderer/src/components/ui/select.tsx:60) renders data-slot="select-content" and Radix gives its content role="listbox", not role="menu". So with a select open, the window-level capture handler ran first, called preventDefault() and closeTaskPage() — closing the whole task page instead of just the select. Added [data-slot="select-content"] to the guard, as suggested. I did not add [role="listbox"]; the reviewer explicitly notes it's too broad, and the data-slot selector covers every select rendered through the shared wrapper.
---------
Co-authored-by: m4air <m4air@MacBook-Air.localdomain>
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
|
||
|
|
4b5157b147 |
fix(codex): bound state DB recovery retries (#13109)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
c3939ebf0e |
fix(mobile): allow reachable Hyper-V pairing addresses (#13107)
* fix(mobile): allow reachable Hyper-V pairing addresses * fix(mobile): keep host-local Hyper-V addresses filtered * fix(mobile): preserve explicit address on empty refresh --------- Co-authored-by: OrcaWin <293788423+OrcaWin@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> |
||
|
|
6b547cf2ea |
fix(ai-vault): contain WSL session deletion (#13106)
* fix(ai-vault): contain WSL session deletion * fix(ai-vault): close approved-root traversal race * test(wsl): restore fixture permissions before cleanup --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
e7d288c58c |
fix(cmd-j): re-rank Recent when terminal entities hydrate late (#13105)
* Fix Cmd+J recent order when terminal entities hydrate late Unified tabs can appear before tabsByWorktree entities on restore, which latched an all-IDLE ranking and buried blocked chats until reopen. Keep a provisional freeze, then re-capture once entities arrive. * Harden Cmd+J incomplete hydration re-rank latch Clear the provisional order latch when the ranked list goes empty so a brief tab wipe cannot freeze an empty Recent section, and assert that a user-moved selection survives the incomplete→complete re-rank. * Fix Cmd+J ordering to not compare focus ordinals across worktrees focusOrdinal is a per-worktree sequence, so comparing rows from different worktrees corrupts their relative order. Preserve input (positional) order instead. Also: refactor test helpers to use makePaneKey() utility for pane-key construction, and clarify a test description about CJK character handling in relevance scoring. |
||
|
|
523feda462 |
fix(commit-message): use Kimi --prompt instead of Claude --print (#11674)
* fix(commit-message): use Kimi --prompt instead of Claude --print kimi-code rejects --print (suggesting --prompt). Deliver the generation prompt as the --prompt argv value so branch auto-rename and commit message generation work when Kimi is the selected agent. Fixes #11669 * test(commit-message): cover Kimi argument defaults |
||
|
|
a7e31e5e10 |
chore(mobile): apply the supply-chain release-age gate to mobile
mobile/ had no .npmrc, so unlike the root workspace it would resolve packages published moments ago. #13113 surfaced this concretely: it pulled nanoid 3.3.18 and postcss 8.5.26 at 0.4 and 1.7 days old, both newer than anything the root gate would have allowed. Copies the root's minimum-release-age=4320 (3 days). Deliberately not shamefully-hoist -- that one is Electron-specific and would change how mobile hoists. Re-resolves nanoid to 3.3.17 and postcss to 8.5.25 in the same commit because the gate is otherwise unusable: pnpm install fails with ERR_PNPM_NO_MATCHING_VERSION on the locked nanoid 3.3.18. Both picks stay above their advisory floors (CVE-2026-67213 needs >=3.3.17, CVE-2026-69153 needs >=8.5.23), so this is not a security regression. Co-authored-by: Orca <help@stably.ai> |
||
|
|
757b785e43 |
fix(deps): resolve Dependabot security alerts across root and mobile (#13113)
Clears 47 of 49 open Dependabot alerts across the root and mobile lockfiles. The 2 remaining (image-size) have no patched upstream release. Direct bumps: pdfjs-dist 5.7.284 -> 6.2.108 (CVE-2026-16633), mermaid 11.16.0 -> 11.16.1 (root + mobile), dompurify 3.4.12 -> 3.4.13. In-range re-resolves: brace-expansion, fast-uri, hono, ip-address, js-yaml 4.3.1/3.15.1, nanoid, postcss, tar, undici 6.28.0/7.29.0. Drops the @modelcontextprotocol/sdk>@hono/node-server override by bumping shadcn's transitive SDK to 1.30.0, which widens its range to ^1.19.9 || ^2.0.5 so @hono/node-server resolves to a patched 2.1.0 on its own. The other two overrides must stay: monaco-editor hard-pins dompurify 3.2.7 and xcode wants uuid ^7.0.3, both vulnerable. pdf.js 6 removed PDFDocumentProxy.destroy(); PdfViewer now tears the document down via the loading task it was already destroying. Supersedes #13074, #13090, #12960, #12952. Co-authored-by: mondaychen <monday.chen@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
4c26ef626c |
Extract GitHub task search commit debouncing into a hook (#13112)
* Extract GitHub task search commit debouncing into a hook Move debounce logic from TaskPage into useGitHubTaskSearchCommit to prevent excessive GitHub API calls on every keystroke. Uses a 750ms idle window before committing search values. Add tests for the new hook. * Keep task rows visible while typing search query Removed premature row-hiding logic from the search input handler that was triggering before debounced queries fire. The handler now only updates the input state; debouncing and query timing are handled by a dedicated hook. Added e2e test verifying search idles before fetching and Enter doesn't double-fetch. * Test that GitHub task search commits cancel on disable and unmount Verify the useGitHubTaskSearchCommit hook properly cleans up pending commits when disabled or when the component unmounts. This prevents unnecessary GitHub API calls during normal user interaction. Also fix e2e test instrumentation to find the active repo through the worktree relationship rather than assuming the first repo with a path. |
||
|
|
597c84f36c |
fix(rate-limits): read Kimi usage credentials from the configured WSL runtime (#12475)
* fix(rate-limits): read Kimi usage credentials from the configured WSL runtime Kimi's usage fetch always read the Windows host's ~/.kimi-code, so a Kimi CLI running inside WSL rotated only the WSL-side token and the status bar was stuck on 'Run Kimi to refresh'. Resolve the Kimi home from the local-account runtime target (mirroring Codex's getDefaultWslDistro()/getWslHome() UNC pattern), pinned to host off Windows, and keep KIMI_CODE_HOME host-only. Fixes #12370 Co-authored-by: Orca <help@stably.ai> * fix(rate-limits): bound and offload the Kimi credentials read for WSL homes Adopted from @cengiz-io's #12372: read credentials through createAuthFilesystemOperation (async, per-path dedup, AbortSignal bound) so a stopped distro degrades to an error instead of parking Electron main on a UNC read. ENOTDIR joins ENOENT as "not signed in" to keep existsSync parity, and the WSL runtime target is now probed with the async wsl.exe helpers. Co-authored-by: Orca <help@stably.ai> * test(rate-limits): build Kimi credential-path expectations with path.join The host-home assertions hardcoded POSIX separators, so they only passed on a POSIX runner — on a Windows dev machine `join` emits backslashes and all four cases failed (three assertion mismatches plus a WSL-suite fixture whose map key never matched the path the fetcher read). --------- Co-authored-by: Orca <help@stably.ai> Co-authored-by: OrcaWin <alpha-eng@stably.ai> |
||
|
|
2c865cda66 |
Add open-tab search to the new-tab omnibox (#13100)
* Add open-tab search to the new-tab omnibox The omnibox now searches workspace, browser, and simulator tabs alongside file creation. Tab results rank first, so users jump to existing tabs before creating new ones. File entries for open editor tabs are suppressed to avoid duplication. * feat(tab-bar): include focused tab in open-tab search The omnibox no longer hides the tab a column is already showing when searching. The + menu can be opened from any column, so filtering by the focused column's visible tab broke search when you tried to find the tab on screen. Now every tab in the worktree is offered, matching how Cmd+J lists the tab you're on. Removes the groupId parameter from useOpenTabSearch since all tabs are now included regardless of which column opened the menu. * refactor(tab-bar): snapshot open-tab search entries and consolidate rank Extract entry building to a pure function in open-tab-search-entries.ts; simplify the hook to snapshot once at menu open rather than subscribing to store changes. Consolidate three ranking functions into a generic rank helper and merge activation routing into a single function with a shared failure handler. Improves code clarity and hook efficiency. * refactor(tab-bar): make open-tab search reactive instead of snapshotted - Tab search results now reflect changes while the menu is open, using a shallow store selector instead of a static snapshot - Separate state selection from entry building for cleaner composition - Use the public API method getKnownWorktreeById instead of internal helpers - Remove tests for snapshot behavior that no longer applies * refactor: extract test fixture and thread execution host for remote work - Extract duplicated lucide-icon stub into shared test fixture - Fix open-tab-search path matching to be editor-only (diff/review tabs have different destinations) - Thread execution host ID through simulator tab palette activation for remote-hosted worktrees * Gate cmdk selection until deferred query updates cmdk reports selections before useDeferredValue commits the new query, leaving the old first result selected. Ignore selection changes until the deferred query catches up. |
||
|
|
96a6c93757 |
fix(orchestration): allow fresh agents to take over legacy runs (#12896)
* fix(orchestration): allow fresh agents to take over legacy runs * test(orchestration): cover forged takeover evidence * fix(orchestration): retain renderer launch authority --------- Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
940f2ff1e4 |
fix(terminal): quote agent resume for the tab's real Windows shell (cmd.exe) (#12476)
* fix(terminal): quote agent resume commands for the tab's real Windows shell
Cold restore and sleeping-agent resume built their launch line without the
host shell family, so win32 fell back to PowerShell argv quoting. On cmd.exe
tabs those quotes arrived literally and agent CLIs rejected the resume argv
and permission flags after a reboot ("unexpected argument ''<uuid>'' found").
Both call sites now share resolveAgentResumeLaunchTarget, which resolves the
launch platform and the live shell family together via
resolveLocalWindowsAgentStartupShell, honoring a per-tab shell override for
cold restore and leaving SSH / remote-runtime / WSL workspaces on their own
default quoting.
Fixes #12320
Co-authored-by: Orca <help@stably.ai>
* test(shared): cover cmd.exe resume quoting at the plan layer
Adopted from #12321 by @CountClaw.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
|
||
|
|
094d6821ef |
feat(native-chat): add model and effort pickers for grok (#12780)
* feat(native-chat): add model and effort pickers for grok Grok had no session-option catalog, so the native chat composer showed no pills and every launch ran the CLI's own defaults with no way to change them. Adds a `GROK_SESSION_OPTION_CATALOG` (model via `-m`/`/model`, reasoning effort via `--reasoning-effort`/`/effort`) and the discovery plumbing behind it. Grok's selectable ids depend on the signed-in account and on `[model.*]` config, so the seed carries only `grok-4.5` and a runtime `grok models` probe supplies the rest as authoritative — a retired id must be droppable, since launching one is a fatal exit rather than a warning. Because `grok models` publishes `Default model:` and marks the row `(default)`, the picker can name the model a fresh session is actually running: `defaultModelIsCliDefault` plus an untracked record means no `-m` was ever emitted, so the CLI is on its own default. That default scopes the effort row but is never written to persisted settings — that field is what authorizes `-m` on every later launch, and adopting a model the user never picked would pin today's default forever, fatally so on an account without it. `grok --help` publishes no default for `--reasoning-effort`, so the effort value stays unnamed until something sets it. Known gap: that refusal to persist is also a limit. An option set while on the CLI default is dispatched and honored in-session, but reaches no later launch — it persists under the default's id with `model` left unset, and both `resolveNativeChatSessionOptionDefaults` and `resolveAgentSessionOptionLaunch` bail without that key. Picking a model explicitly persists normally. Closing this means teaching both to resolve options from the default model while still refusing to emit `-m`, which is the launch-args path and wants its own review. Known gap: the picker infers "no `-m` was emitted" from its own in-memory record, so a model reaching argv from outside it — the user's own `agentDefaultArgs`, or a renderer reload that drops the record while the flagged PTY lives on — leaves the pill claiming the CLI default while another model runs. No wrong model is persisted. Extracts `hasFlag` and `labelFromModelId`, and splits the model-probe spec out of the commit-message registry so discovery no longer implies an agent can write commit messages. Co-authored-by: Orca <help@stably.ai> * docs(native-chat): note the invariant keeping modelIsCliDefault agent-safe The flag is computed without checking the catalog, so it reads as unsafe for the four agents with no CLI default. It is safe only because `persist` bails unless `modelId` is truthy, which for those agents implies a tracked model. Widening that guard would silently change persistence for every agent. Co-authored-by: Orca <help@stably.ai> * fix: retire persisted models on mount and handle -- terminator - When a pane mounts after model discovery has already settled, it now checks the cache and retires persisted models that are no longer available. - CLI flag detection now respects the `--` option terminator, treating everything after it as positional arguments rather than flags. * Fix: persist grok session options under probe-confirmed defaults Options set under the CLI default were silently lost on restart. Distinguish seed guesses from probe-confirmed defaults by renaming `modelIsCliDefault` to `modelIsUnverifiedDefault`. Once confirmed, adopt the default as a persisted flag so options survive restarts. * fix(native-chat): close the retired-model fatal-launch paths from counsel review Counsel report C1/C2 (High), C3, P1, C4: - Untrack a session model an authoritative discovery dropped and gate every persist path, so option writes can never re-adopt a retired id (C1). - Resolve launch defaults through the enrichment cache: a persisted model missing from every settled probe no longer becomes a fatal `-m` (C2). - Serialize retirement and picks on one settings write queue that re-reads live state at apply time (C3). - Stabilize onSwitchToTerminal so the session-option surface is not rebuilt every TerminalPane render (P1), and cap the enrichment host map (C4). Co-authored-by: Orca <help@stably.ai> * Store agent in enrichment entry and extract token utilities Refactor enrichment to store the agent field directly instead of parsing it from a composite key, and extract CLI flag token filtering into a shared utility. Use a dedicated function for tracked model ID lookup. Improves code reuse and reduces parsing overhead. * Rename modelIsUnverifiedDefault to adoptModelAsLaunchDefault Move the model adoption gate into the core session-options module, where probe confirmation and discovered-model status are known. This ensures adoption decisions are gate-checked before persisting to avoid fatal launch flags, and simplifies the picker surface by moving the logic to where it belongs. * Keep model probe evidence by agent, not host Store probed model IDs in agent-keyed cache independent of host cache, so evidence persists across host eviction. Prevents retired models from being treated as valid when host cache entries are evicted. * Store agent in enrichment entries instead of separate proof-evidence map Model probe evidence is now tied to enrichment entries rather than maintained in a separate per-agent map, eliminating the need for eviction logic that could disconnect proof from entries. --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
8cdfa64e68 |
fix(grok): keep the Windows hook safe under an inherited /v:on (#12479)
A bare `setlocal` inherits delayed expansion from the caller, so every `!` in a percent-expanded value on the curl line is eaten as a delayed reference -- mangling paneKey and dropping worktreeId. `!` is legal in a Windows path. Also adds the test file the script never had: structural checks that run on every platform, plus real cmd.exe runs covering unset/empty/normal/trailing backslash/envelope/`!`/inherited `/v:on`. Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
e6e197feed |
Add Recent Chats & Terminals to Cmd+J palette with digit shortcuts (#13076)
* Add Recent Chats & Terminals to Cmd+J palette
On empty query, the palette leads with recently accessed chats and terminals,
addressable via ⌘1–⌘9 shortcuts. Digit chords are intercepted while the
palette is open, preventing workspace switches behind the overlay. Status
dots reflect agent blocking and activity.
* Fix Cmd+J recent order capture and backfill behavior
- Capture unfiltered recent tab order when palette opens, avoiding frozen filtered subsets on search reopen
- Backfill recent section when rows drop out (mid-open narrowing) instead of rendering empty
- Move create-workspace action to the end as a fallback, not competing with real matches
- Add test coverage for order freezing, backfill, and create-action ranking
* Fix Cmd+J selection backfill when recent tabs hydrate late
Track auto-selected item to distinguish user-moved selections from auto-picks. When recent tabs arrive after the palette opens, reset selection to the top item unless the user explicitly moved it. Use useLayoutEffect to capture the pre-hydration worktree order before tabs render, preventing flashing. Fix worktree budget cap for sessions with no open tabs. Add test coverage for late hydration and user-moved selection preservation.
* Rank Cmd+J results by match relevance, lead with stronger section
Adds match-relevance scoring to rank search results by which field matched and
match position (prefix > word-start > mid-word). When a typed query has results
in both worktrees and open tabs, whichever section holds the stronger hit now
leads the list, matching user intent — a prefix hit in a tab beats a mid-name
hit in a worktree.
Decouples live status dots from the palette body subscriptions to prevent the
whole list from re-rendering on every agent transition or pane-title change.
Dots now own their subscriptions via PaletteLiveStatusProvider, while the body
reads status maps as a snapshot, refreshed only when the palette opens or tabs
change. Freezes the snapshot identity during animation to keep the selection
stable even while closing.
* Handle decomposed accents in Cmd+J match relevance scoring
Include Unicode combining marks (\p{M}) in word-boundary detection so
decomposed characters like café are treated as word boundaries instead
of mid-word matches.
|
||
|
|
38275c2aa2 |
fix(codex): publish Windows system-default sessions (#12611)
* fix(codex): publish Windows system-default sessions * fix(codex): close launch-scheduling races in session migration scheduler * fix(codex): bound repeated session migration audits * fix(codex): preserve delayed session publication passes * fix(codex): bound failed session audit events * fix(codex): fence stale session migration markers * chore: preserve main formatting after merge * perf(codex): bound launch session migration scans * perf(codex): preserve coalesced migration scope * fix(codex): preserve scheduled migration recovery * fix(codex): preserve session migration recovery * fix(codex): close session migration launch races * fix(codex): harden session migration completion |
||
|
|
20aeb0cb99 |
Prepare mobile 0.0.42 and fix TestFlight CI hang (#12961)
* Bump mobile app.json to 0.0.42 * fix(mobile-ios): stop TestFlight CI from waiting on ASC processing 0.0.42 builds 1–2 uploaded successfully then hung for hours polling processing with no Ready build and no Apple email. Exit after upload and cap the job at 90m so the next cut does not repeat that hang. * fix(mobile-ios): fully skip Pilot wait (no changelog) Pilot only returns immediately after upload when changelog is nil; passing notes re-enters the ASC build-list poll.mobile-android-v0.0.42 |
||
|
|
7a867f12aa |
Revert "Fix grok stale pane width (#13060)" (#13098)
This reverts commit
|
||
|
|
9b70ce0424 | fix(computer-use): settle macOS permission checks (#13025) | ||
|
|
2a93cae293 | fix(crash-reporting): stop filing Windows OS shutdown as renderer crashes (#12938) | ||
|
|
f8786d5224 |
fix(agent-history): fold trailing-slash, NFD/NFC, and project-fallback folder group keys (#12458)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
2396e5e3e5 | fix(browser-pane): reschedule remote stream restart with bounded backoff (STA-3483) (#12787) | ||
|
|
360c496b9b |
fix(agent-map): draw every visible orchestration edge, and let the filter hide them (#13087)
The agent map dropped real parent -> child dispatch edges and mislabeled the ones it kept. - Remove the `child.y <= parent.y` gate on agent lineage. Both endpoints are drawn nodes, so the relationship is real whatever the layout ranked them; the gate silently hid edges that packing pressure placed side by side. `lineagePath` is now direction-aware so an upward edge does not exit the wrong side and draw back through both nodes. - Fix the relation attribute. `parent.card.parentPaneKey ? 'subagent' : ...` asked whether the PARENT has a parent and said nothing about the child, so a 3-deep chain rendered solid as if it were an in-process subagent. Every map node is a top-level pane agent -- build-dashboard-snapshot folds subagent rows into the parent card's roster and never makes them cards -- so every card-to-card edge is an orchestration edge. Dead CSS removed. - Add a "Orchestration links" toggle under Filter > Map content, default on, hiding both same-worktree and cross-worktree edges while keeping the nodes. Lineage still comes from the existing active-or-recently-settled dispatch context; making it historical would be a main-process change and is out of scope here by design. Extractions are max-lines pressure, not drive-by refactors: the toolbar hit 404/400 and AgentMapCanvas 403/400, and the repo forbids new max-lines suppressions. |
||
|
|
9e948fbdf4 | release: v1.4.177-rc.0 v1.4.177-rc.0 | ||
|
|
6aafb1d318 |
fix(gitlab): include bridge/child pipeline jobs in Checks (#12863)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
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 |
||
|
|
8c3e9535c7 | fix(terminal): remove permanent link tooltip gap (#13075) | ||
|
|
2e199cb2dd |
fix(ai-vault): tolerate unknown session agents (#12999)
* fix: tolerate unknown AI Vault agents * fix: tolerate unknown AI Vault scan issues * test(ai-vault): cover malformed unknown-agent rows --------- Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
6382b8a053 |
fix(file-explorer): report the saved filename in the download toast (#12959)
* fix(file-explorer): report the saved filename in the download toast The success toast named the remote node, so renaming a file in the native save dialog left the label disagreeing with its own Open action, which opens the real destination. Folder downloads had the same gap whenever sanitizeLocalDownloadFilename rewrote the remote basename. * fix(file-explorer): respect local download path semantics --------- Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
a77002c42b |
feat(ai-vault): delete a provider session from the AI Vault list (#10249)
* feat(ai-vault): validate session-delete targets for single-file providers Add the pure judgement layer for deleting an Agent Session History entry. `validateAiVaultSessionDeleteTarget` decides whether a session may be removed: the agent must be one of the nine providers where a single file is the whole session (gemini, copilot, cursor, hermes, devin, openclaw, droid, pi, omp), the host must be local, and the renderer-supplied path must resolve inside that agent's own session roots and match its discovery predicate. To keep the delete roots from drifting from the scanner's own roots, the WSL-expansion helper moves to session-scanner-root-dirs.ts and the OpenClaw root derivation + session predicate become shared helpers that discoverOpenClawFiles itself consumes. The result is path-only and never touches the filesystem; a returned `allowed: true` still requires an lstat/realpath re-check in the executor (S-2) before removal, documented as a caller contract on the result type. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i * feat(ai-vault): move a validated session transcript to the trash Add the filesystem executor behind session deletion. It calls the S-1 path validator, then performs the fs-side guards that validator documented it could not: lstat().isFile() rejects a directory or symlink, and realpath is re-fed through the validator so a regular file reached through a symlinked parent that escapes the agent's roots is rejected too. Only then is the file moved to the OS trash via shell.trashItem, with ENOENT treated as success so a delete racing an external removal stays idempotent. WSL UNC paths (no Recycle Bin) are delegated to tryDeleteWslUncPath before the Windows-local fs guards, mirroring fs:deletePath. Any non-ENOENT error is returned as a failure result rather than thrown, since IPC payloads are untyped at runtime. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i * feat(ai-vault): delete-session IPC handler, preload bridge, cache invalidation Wire the S-2 delete executor to an IPC endpoint and expose it on the preload bridge. The renderer calls aiVault:deleteSession with { agent, filePath, executionHostId }; the handler fetches WSL homes, delegates to the executor (which re-validates and trashes), and on a real delete invalidates the caches that could otherwise keep serving the deleted session. Cache invalidation is generation-guarded: a scan already in flight when the delete lands carries an older generation and must not write its pre-delete result back into the cache. Without this, an in-flight scan resolving just after the delete would resurrect the deleted session for the 15s TTL — and force-refreshing the panel only masks it for the desktop, not for the paired mobile client or runtime RPC that share the same cache module. Both the shared local-scope cache and the desktop multi-host cache carry the guard, with regression tests for the in-flight race. The delete result type moves to shared/ai-vault-types.ts so the renderer can import the same contract the executor returns. To keep ai-vault.ts within the max-lines budget after adding the delete wiring, two cohesive pieces are extracted to their own files: the delete orchestration (ai-vault-delete.ts) and listAiVaultSubagentSessions (ai-vault-subagent-list.ts). The latter is the only handler with no dependency on this module's private cache state, so it is the one piece that moves verbatim without threading state through a seam. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i * feat(ai-vault): renderer judgement for whether Delete is offered Add the renderer counterpart to the main-side delete validator: given a session, decide whether the row menu shows Delete enabled, or disabled with a reason a tooltip can render. It reuses the shared deletable-agent set and unsupported-reason map so the two sides can never disagree about which agents are deletable, and reuses the existing local-host / synthetic-path renderer helpers. This is intentionally not a security boundary — it validates neither the path root nor the file predicate. Those are the main process's untrusted-input defense; the renderer only picks the affordance, and the main side re-checks on delete regardless. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i * docs(ai-vault): correct deletability parity claim; test multi-reason agent The renderer deletability check runs host -> synthetic -> agent, while the main validator runs agent -> host -> synthetic. The two layers agree only on deletable-or-not (renderer-false is a subset of main-false), not on the reason code a doubly-failing session carries. Document that explicitly instead of implying the orders match, and add the antigravity case (two reason codes) so the agentReasonCodes array shape is actually exercised. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i * feat(ai-vault): add Delete to the session row menu with a confirmation dialog Wire the delete affordance into AI Vault. Both the dropdown and the context menu gain a destructive Delete item; a session that can't be completely deleted (remote host, synthetic OpenCode-SQLite path, or a directory/registry-backed agent) shows the item disabled with a reason surfaced both as a tooltip and as an aria-label so keyboard and screen-reader users learn why. Confirming opens a dialog that names the session and states it will no longer be resumable from the provider's own CLI, then calls the delete IPC and force-refreshes the list for immediate feedback (the main side has already invalidated its caches). The confirmation copy says the session "will be deleted" rather than "moved to the trash": on Windows a WSL session is deleted with rm inside the distro (no Recycle Bin), so promising recoverability would be a lie on that platform. Deletability is computed once per row and shared by both menus so they can never disagree. New pure logic — the reason-to-tooltip mapping (including the multi-reason join) and the delete action hook's deleted/rejected/failed branches — is covered by unit tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i * fix(ai-vault): state that Delete is unavailable without naming the cause The disabled Delete item explained a provider's storage layout to the user ("Claude sessions can't be deleted here: stores sessions as a folder, not a single file"). That is Orca's problem, not the reader's — the tooltip now says which sessions are affected and stops there. The non-local-host string stays as it was: it states scope, not a cause, and tells the user what would work. The reason-code plumbing existed only to compose that tooltip, so AI_VAULT_UNSUPPORTED_DELETE_REASONS, AiVaultUnsupportedDeleteReasonCode, and the renderer result's agentReasonCodes field go with it. Why each agent is excluded moves into the comment above AI_VAULT_DELETABLE_AGENTS, where a reader looking up the deletable set will find it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGuzChimmQ1dYX2raecrH7 * feat(ai-vault): delete claude, rovo, and grok sessions by their directory These three were excluded only because the delete unit was one file. Their sessions are directories — claude keeps Task subagent transcripts in a sibling `<uuid>/subagents/`, rovo and grok keep everything under `<sessionId>/` — and nothing in them is shared with another session, so a directory-aware delete is still a complete delete. Supported goes from 9 agents to 12; the four that remain (antigravity, kimi, codex, opencode) are blocked by a registry or a SQLite row, which no delete unit fixes. Validation now returns an ordered removal plan instead of a single path. Each removal carries the kind it must be on disk and the roots its realpath must stay inside, so the executor's guard is the same shape for a file and for a directory. Companions come first and the transcript last: the transcript is what puts the row on screen, so a part-way failure leaves the row to retry from rather than dropping it and stranding the rest on disk. Claude's `session-env/<uuid>/` goes with the transcript — it holds that session's generated shell exports and nothing else. Its sibling `file-history/<uuid>/` deliberately does not: it is the rewind buffer holding earlier versions of the user's own files, and retiring a session is no reason to take away the only copy that can restore them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGuzChimmQ1dYX2raecrH7 * fix(ai-vault): remove a claude session's own directory, not just its subagents Deleting a claude session trashed `<uuid>/subagents/` and left `<uuid>/` behind as an empty directory — one per deleted session, accumulating under every project. The directory is named after the transcript, so it belongs to that session as a whole; take it rather than the one subdirectory inside it. Still derived from the scanner's own subagents path, so the two cannot drift. Reaching the parent means a degenerate stem now matters: `..jsonl` passes the extension check and its stem is `.`, which would resolve the session directory to the project directory holding every session. Reject it instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGuzChimmQ1dYX2raecrH7 * fix(ai-vault): keep a session row collapsed when a menu action is chosen Radix portals the row's dropdown and context menus out of its DOM, but React still bubbles their clicks back through the component tree, so every menu selection also hit the row's own click handler and expanded it. The trigger button already stopped propagation, which is why opening the menu looked fine and only choosing an item misbehaved. It shows worst on Delete: the row expands behind the confirm dialog, so cancelling leaves the list rearranged under a dialog the user just backed out of. Toggle details only for clicks that land in the row's own subtree — that covers the context menu and any future portalled surface, not just this one. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGuzChimmQ1dYX2raecrH7 * fix(ai-vault): harden the delete-confirmation flow against IPC rejection and mid-delete dismissal Two robustness gaps flagged in review: - handleConfirmDelete only branched on result.outcome. The main handler resolves with a 'failed'/'rejected' outcome rather than throwing, but the IPC invoke itself can still reject on a transport/serialization error, and the caller fires it with `void`. That reject would surface as an unhandled rejection with no toast. Catch it and show the same generic failure toast. - handleDialogOpenChange cleared sessionPendingDelete on every open=false. The Cancel button is disabled mid-delete, but Radix still fires its Escape/outside-click/X close, which could dismiss an in-flight delete out from under itself. Ignore close requests while deletingSession is true. Both covered by regression tests (verified failing without the fix). * fix(ai-vault): route WSL UNC directory removals through the WSL rm branch Directory-shaped deletes (claude's subagents/session-env dirs, rovo/grok's session dir) gated the WSL branch on kind === 'file', so on Windows a session under a WSL distro home fell through to shell.trashItem — which can't trash a WSL-volume item (no Recycle Bin) and throws, or worse is silently stranded when the 9P filesystem's unreliable lstat false-reports ENOENT and the executor treats that as success. Single-file deletes predate the directory kinds, so the file-only gate was correct until directory removals were added. tryDeleteWslUncPath already supports recursive removal; pass recursive for directory removals so they take the same WSL rm path as files instead of shell.trashItem. Covered by two regression tests (file: non-recursive, directory: recursive), verified failing without the fix. Also drops the internal ledger-ID references (D-*, S-*) from comments in these two files; they pointed at a private design doc a reader can't see. * docs(ai-vault): drop internal design-ledger IDs from shipped comments Comments across the session-delete feature cited decision/slice IDs (D-1..D-7, S-1..S-5) from a private design document. Those references are meaningless to anyone reading the code without that doc, so remove the IDs while keeping the reasoning each comment carried. No behavior change. * test(ai-vault): e2e-cover the real on-disk session delete The unit tests mock lstat/realpath/trashItem, so nothing proved the whole IPC path actually removes files. This spec seeds sessions into the E2E harness's isolated HOME and deletes them through window.api.aiVault.deleteSession: - a single-file session (gemini): the transcript is gone from disk and drops out of the list. - a directory-shaped session (claude): the transcript, the <uuid>/ session directory (subagents included, no empty shell left), and the session-env companion are all gone, while the file-history rewind buffer is preserved. Verified failing when the executor's removal is stubbed out. Runs on Linux CI. * fix(ai-vault): address review findings on the session-delete flow Three points raised in review: - Disable Delete for a still-running session. resolveAiVaultSessionDeletability now gates on liveState (working/blocked/waiting) last — an otherwise-deletable session that is mid-run shows "wait for it to finish" instead of an enabled Delete, so trashing a live agent's transcript can't drop writes it is still appending. Unsupported/remote sessions keep their permanent reason. - Realpath the roots, not just the target, in the executor's escape check. The roots were only resolve()'d (text), so a session under a symlinked root (~/.claude -> /Volumes/…) was falsely rejected; realpath each root (falling back to its text form when it can't be resolved) before the membership check. - Invalidate the parse cache with the raw filePath, not resolve(filePath). The cache is keyed by the exact path the scanner discovered, so resolve() could normalise it away from the stored key and miss. Drops the now-unused import. Also moves AiVaultDeleteSessionArgs/Result out of ai-vault-types.ts (which the upstream merge pushed over the max-lines limit) into the ai-vault-session-deletion domain module they belong to, and updates importers. Regression tests added for the live gate, the symlinked-root accept, and the reason string; verified failing without each fix. * fix(ai-vault): type the deleteSession preload bridge as its real result The bridge declared Promise<unknown> while AiVaultApi.deleteSession promises AiVaultDeleteSessionResult, so the preload object leaned on the api-types declaration to stay honest instead of being checked against it. Co-authored-by: Orca <help@stably.ai> * refactor(ai-vault): tighten the session-delete code to house style Comments across the delete flow explained HOW alongside WHY and ran to a dozen lines; they now carry only the non-obvious reasoning. The excluded-agent rationale, the caller contract on the validator, and the file-history carve-out are kept — those are knowledge, not narration. Also removes three duplications the feature introduced: - AiVaultSessionDeleteExecutionResult was an alias for AiVaultDeleteSessionResult whose comment pointed at a module the type no longer lives in. - The synthetic-path predicate existed twice under near-identical names; the renderer now re-exports the shared one it already had a sibling import of. - The delete-failure toast was written out verbatim in both the rejected and the thrown branch. Co-authored-by: Orca <help@stably.ai> * refactor(ai-vault): use a design-system dialog width and a stable row selector The confirm dialog pinned an arbitrary sm:max-w-[440px]; every other dialog in the right sidebar uses a scale token, and md (448px) covers the role. The row-expand test selected the row by [draggable="true"], which stopped naming the row when draggable moved to the title element upstream. It still passed by bubbling, so the comment was the only thing wrong — now it selects the title deliberately and says why the query is first-match (Radix's asChild trigger repeats the subtree, so screen.get* sees duplicates). Also types the e2e delete helper as AiVaultDeleteSessionResult instead of a hand-written { outcome: string }, now that the preload bridge returns it. Co-authored-by: Orca <help@stably.ai> * refactor(ai-vault): consolidate agent sources and use system dialog Discovery and deletion now share the same agent source definitions, eliminating the risk of them drifting apart. A single `AI_VAULT_AGENT_SOURCES` table declares each agent's root directories, file extensions, and acceptance predicates. Replaced the custom delete confirmation dialog with the system dialog, simplifying the delete action hook and removing boilerplate state management. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
58531e4caa |
Fix grok stale pane width (#13060)
* fix(terminal): restore canonical terminal rows from unified sessions During session hydration, non-canonical terminal rows (closed/stale tabs) were being restored alongside canonical ones. Filter to only canonical terminal rows from unified sessions, treating legacy rows as terminal backing data only. * fix(terminal): preserve independent legacy PTY rows |