* fix(remote): distinguish transport from runtime availability
* fix(remote): preserve transport diagnostics for unavailable runtime
* fix(remote): propagate transport diagnostics to host setups
* fix(remote): keep unavailable runtimes out of ready setups
* fix(remote): preserve unavailable runtime state in settings
* fix(remote): preserve reconnecting runtime state
* fix(remote): guard stale settings connectivity
* fix(remote): preserve diagnostics after main merge
* fix(i18n): preserve translations during runtime status merge
* fix(remote): refresh settings row health from store
* fix(remote): refresh settings row health from store
* fix(remote): clear diagnostics generations in tests
* fix(settings): refresh runtime availability summary
* refactor(runtime): split status slice types
* refactor(runtime): reuse status app state type
---------
Co-authored-by: Merge Sim <sim@local>
* perf(renderer): avoid combined-diff tree rebuilds during progressive loads
* fix(renderer): preserve collapsed combined-diff tree boundaries
* perf(renderer): skip unfiltered combined-diff flatten when hiding viewed files
* fix(renderer): keep reordered viewed keys in the combined-diff delta
The incremental viewedSectionKeys delta walked indices issuing a delete
then an add, so a key added at index i and deleted as the previous key at
a later index was silently dropped. Fall back to a full recompute when any
index's key differs; the progressive-load fast path (stable keys, flipping
loading state) is unchanged.
* perf(terminal): activate splits before cwd resolution
* test(terminal): prove split focus before cwd publish
* fix(terminal): release stale split cwd fence
* test(terminal): add visible split activation latency benchmark
* docs(reliability): clarify split benchmark provenance
* fix: preserve deferred split handoffs across remounts
* fix: fence late deferred split closes
* docs(reliability): record exact split benchmark runs
* test(reliability): fail benchmark on artifact write errors
* test(reliability): attribute split activation phases
* docs(reliability): record schema-v2 split benchmark
* refactor(terminal): collapse duplicated split-handoff and write-queue paths
- Drop the discardDeferredSplitPaneHandoff alias for its identical clear twin.
- Fold the deferred-cwd resolve/reject settle handlers into one applier.
- Extract settlePaneCwdDeferredSpawn for the repeated read-clear-write pattern.
- Share one head-index FIFO primitive between the ordinary and reply queues.
* fix(terminal): stop retaining a promise reaction per acknowledged write
Racing every accepted write against one queue-lifetime cancel promise kept a
reaction record alive until that promise settled: 200k acknowledged writes
retained 88.6MB, now 0.1MB. Give each in-flight write its own cancel, and
split the shared FIFO primitive into its own module.
Also sanitize the split-latency benchmark report at its single serialization
point so shared artifacts no longer carry the machine-local repo path or
unbounded cleanup error text.
* fix(terminal): settle deferred split input when the spawn is abandoned
An abandoned deferred spawn returns before transport.connect(), so nothing
drained the pre-connect buffer: sendInputAccepted's promise never settled and
a paste into that pane hung forever. Clear the buffer on the abandon fence.
Also re-derive the pre-connect retention cap from the clipboard-paste ceiling
rather than the 16MB single-write ceiling; it is held twice per pane across up
to 64 deferred splits, so 5.59M code units guarded the wrong thing.
* fix(terminal): release the deferred cwd fence on a rejected reattach
A daemon createOrAttach can turn an apparent fresh spawn into a reattach; when
that reattach is refused the spawn ends with deferredSplitSpawn/pendingCwd
still set, permanently arming the pre-bind detach refusal. The release no-ops
when a PTY did bind, so it only fires where the fence would otherwise leak.
The stale-generation return above is deliberately left alone: a newer connect
already owns the pane there, and the fence is not generation-scoped.
* Show live tool progress in native chat
* fix(native-chat): scope live tool indicator to current turn
* fix(native-chat): settle orphaned live tool rows
* fix(native-chat): keep live tools running without lifecycle metadata
* fix(native-chat): keep working status stable during streaming
* fix(native-chat): anchor turn status below prompts
* fix(native-chat): preserve turn status and legacy tool activity
* fix(native-chat): limit turn status UI to structured Codex
---------
Co-authored-by: Merge Sim <sim@local>
* fix(worktrees): preserve user workspace names across branch changes
* test(worktrees): cover pinned rename metadata
* fix(workspaces): address display-name review edge cases
* fix(workspaces): keep automatic names fresh across refreshes
* fix(workspaces): preserve legacy CLI labels
* fix(workspaces): preserve display-name provenance across hosts
* fix(workspaces): honor legacy display-name provenance
* fix(workspaces): fence display-name refresh races
* fix(workspaces): accept peer renames from provenance-less hosts
The old-host preserve fence kept a pinned local label on every refresh,
which also suppressed a legitimate rename another client persisted
through the same host until app restart. Narrow it to labels the host
re-derived itself (branch short name, or path basename when detached);
any other changed label in a mode-less response is explicit meta a peer
wrote there. Stale prior-label responses stay covered by the downstream
staleness fence, in-flight writes by the pending fence.
* refactor(workspaces): unify display-name pin derivation
Three call sites (renderer optimistic update, local IPC updateMeta
handler, remote worktree.set handler) each restated the same formula;
a future edit to one would silently skew provenance between paths.
* feat(ssh): batch process evidence in PTY inventory
* fix(ssh): accept Linux kernel process rows and make no-evidence polling push-driven
* fix(ssh): preserve process evidence polling semantics
---------
Co-authored-by: Merge Sim <sim@local>
* fix: satisfy GitLab hook and test lint gates
* Rename electron-vite target config to .cts
The .cts extension keeps the config as CommonJS, allowing electron-vite
to load each parallel target without sharing its timestamp-named ESM
temp file.
* feat: add show-more button for recent tabs in empty-query palette
* Reveal all recent tabs in one show-more click, limit badges to 9
The show-more button now expands the entire recent tabs list instead of
paging through it. Badges are limited to the first 9 rows since only
those digit positions are addressable in the keyboard chord.
* perf(diff): defer large diffs until user loads them
Rendering very large diffs would freeze the UI. Diffs exceeding
MAX_AUTOMATIC_DIFF_CHANGED_LINES now show a prompt allowing users
to load them on demand instead of automatically rendering.
* perf(diff): defer large diffs until user loads them
Diffs with >10,000 changed lines are now deferred and only rendered when
the user explicitly clicks "Load diff" in a prompt. This improves initial
render performance for large file changes while maintaining full access
when needed.
* perf(diff): defer large diffs until user loads them
Prevents UI freeze when opening files with very large diffs by
deferring render until the user explicitly loads them.
* fix(diff-view): defer loading large untracked files and refactor fallbac
Split on-demand load decision logic to distinguish tracked vs untracked files — large untracked files now properly defer loading while untracked images remain automatic. Extract fallback height computation into a dedicated function to centralize the logic for render-limited and in-flight-loading states, reducing code duplication and clarifying when to use bounded fallback heights.
* fix(diff-view): defer loading large SVG files
SVG renders as source text in the diff view rather than a preview, so should defer like other text files. Also fix Windows e2e test cleanup by using post-Electron shutdown.
* fix(ssh): fence stale kills and retired pane replay
* fix(ssh): support cancellable interactive authentication
* fix(ssh): await remote catalog before snapshot adoption
* fix(pty): contain Windows ConPTY input failures
* fix(power): avoid redundant macOS display blocking
* perf(editor): narrow markdown override subscriptions
* fix(quick-open): close directory handles after reads
* refactor(linux): remove unused proc socket scanner
* fix(usage): apply flat Sonnet 4.6 pricing
* ci: prime Node next native test cache
* docs(skills): resolve snapshot cleanup data path
* fix(ssh): recover install locks after host reboot
* test(ssh): recognize boot-aware install locks
* test(ssh): prove previous-boot lock recovery live
* test(wire): pin pre-metadata release coverage
* fix(terminal): preserve remote tab ownership through recovery races
* test(runtime): fence replaced terminal handles in agent guard
* fix(ssh): preserve remote snapshot authority across polls
* fix(pty): contain late ConPTY output EPIPE
* test(pty): register Windows exit watcher before kill
* fix: close SSH and tab readiness race gaps
* fix(tabs): retain headless order and placeholder titles
* fix(build): avoid parallel electron-vite config race
* test(windows): avoid MSYS temp path rewriting
* test(windows): avoid killing exited PTY
* fix(pty): avoid late ConPTY input teardown race
* fix(terminal): sync reconnect error ownership after commit
* fix(runtime): use canonical worktree identity comparison
* test(ssh): assert complete cold-hydration baseline
* test(windows): invoke quoted retention fixture via PowerShell
* test(windows): read ConPTY grid through mode con
* fix(terminal): publish PTY replacements atomically
* fix(terminal): infer stale identity on reattach
* fix(terminal): fence stale pane PTY callbacks
* fix(terminal): fence stale pane binds after rebind
* fix(terminal): reject stale pane transport callbacks
* fix(terminal): fence mirrored reattach spawn callbacks
* fix(terminal): replace stale pane PTYs on remount
* fix(ci): size the Windows launcher-compile test budget from measurement
`native-smoke (windows-latest)` fails ~4.5% of runs on
`preserves a multiline argument through the compiled remote launcher`
with "Test timed out in 15000ms" — on unrelated PRs, for reasons that
have nothing to do with them. Across 176 sampled attempts it is the only
red that job produced, and it hit seven different PRs in two days:
#16900, #16904, #16915, #16955 (twice), #16979, #17014, #17085.
The test is six process creations: powershell.exe forks csc.exe, then
the freshly compiled orca.exe forks node.exe, twice. Hosted Windows
runners periodically slow process creation down, and this test amplifies
that far harder than anything else in the job. Comparing the 80 attempts
where it ran under 3s against the 12 where it ran over 12s, its own
median goes 2198ms -> 15917ms (7.2x) while the same file's
powershell-only test moves 556 -> 686ms (1.2x), the cmd.exe and Git Bash
process tests in the neighbouring file move 1.4x, and the other 35 files
put together move 1.5x.
Measured across those 176 attempts: 1881ms to 35438ms, p50 4264ms,
correlation +0.881 with the job's total Vitest duration. 8 of 176 (4.5%)
exceeded the 15s cap; 2 of 176 (1.1%) also exceeded the shared 30s
testTimeout, so deleting the override and inheriting the config is not
enough on its own. 60s clears all 176 with 1.7x headroom on the worst.
This is slow, not hung. Every body here is synchronous spawnSync, so
Vitest cannot interrupt one — the timer fires only after the body
returns and the reported duration is real elapsed time. That is why a
failure reads `× ... 22464ms` under `Test timed out in 15000ms`. The
work finished; the stopwatch was short. Seven reruns at one identical
head measured 2053 / 4680 / 5551 / 8732 / 13506 / 14868 / 21937ms — the
last of those would have been red on code that had not changed.
The 15s came from #8897, which raised this test off Vitest's built-in 5s
default because the job then ran bare `pnpm vitest run`. #8909 landed
3h27m later and pointed the job at config/vitest.config.ts, which is the
real fix for that. The constant stayed behind and has been the binding
budget ever since.
* fix(terminal): fence stale remount reattach ownership
* fix(terminal): reconcile mounted pane identity after replacement
* fix(terminal): fence stale reattach fallback ownership
* fix(terminal): fence deferred SSH reattach ownership
* fix(terminal): fence stale split pane ownership callbacks
* fix(terminal): keep stale spawns from consuming startup
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(worktree): gate agent activation on the live surface census, not renderer state (STA-5701)
* fix(worktree): seed a pane when the surface census cannot prove ownership (STA-5701)
Failing closed must not also fail silent. When the census is unverifiable
the sweep adopts nothing and mints nothing, yet the gate still reported
'adopted' — and both callers suppress their own seeding on any outcome but
'empty', so the workspace ended with zero surfaces. The sweep now reports
whether any live PTY holds a surface and the gate hands the caller its seed
when none does. Also folds equivalent workspace-path spellings in the census
index and in exact-surface binding, so a host row spelled differently is
neither dropped (mint a duplicate) nor unbindable (no pane).
* fix(worktree): name the live PTYs the surface census declined (STA-5701)
The adoption sweep can leave a live PTY without a surface — an unreadable
census, two host surfaces claiming one PTY, or a host-named leaf the
persisted layout does not have. The gate already stops reporting 'adopted'
in that case so the caller seeds a shell, but the decline itself was mute.
- adoptLiveWorkspacePtySurfaces now returns { surfaced, declinedPtyIds }
and the gate warns with the workspace and the PTY ids left unsurfaced.
- Pin the host-named-leaf decline, which had no test either way.
- Pin the superseded-inventory race in terminal.list: a concurrent refresh
makes hostScope.hostIds empty, which is what makes the renderer's
'unverifiable' verdict reachable on a plain local machine.
* perf(wsl): warn at project-add when the tree sits on a Windows drive
Worktree placement now puts new workspaces inside the distro, but a project
whose own tree is on C:\ still pays the 9p/drvfs crossing on every git command
it runs — measured at ~20x for a clean `git status` against the same tree on
ext4. Nothing in the UI says so, so the project just feels slow.
Warn once, right after the add succeeds, naming the distro the project's git
actually runs in. The advisory is wrapped so it can never fail the add.
Two path shapes cross the boundary and both warn: a Windows drive path under a
WSL project runtime, and the UNC spelling of a distro's own drvfs mount
(\\wsl.localhost\Ubuntu\mnt\c\...), which crosses it however the runtime is set.
A tree already inside the distro, a drive path under Windows-host git, a plain
UNC share, and every POSIX/SSH path stay silent.
* chore(i18n): register the WSL filesystem boundary advisory keys in en.json
Track scroll-restore generation to invalidate stale callbacks that were
resetting the scroll position to 0 when reopening a detail page. Prevent
restoration while a detail page is open.
Update automation test to use runtime.call RPC instead of removed preload
CRUD method. Hide browser import hint in E2E profile to prevent overlay from
intercepting test setup clicks.
Validate scheduled values match current state before executing idle
timeout callbacks. Use useLayoutEffect to synchronously update refs,
preventing outdated searches when rapid keystrokes overwrite timers.
* refactor(agent-hooks): drop the unused per-agent hook status IPC surface
No renderer, CLI, or mobile caller invoked window.api.agentHooks.*Status; main
already reads install status through MANAGED_AGENT_HOOK_STATUS_READERS. The
14 handlers had also drifted (kimiStatus existed in main/preload but not in
AgentHooksApi or the web stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(tui-agent-config): default launchCmd and expectedProcess to detectCmd
32 of 36 entries repeated the binary name three times. Entries are now
authored in a source form where both default to detectCmd and resolved once
at module load, so TUI_AGENT_CONFIG keeps its exact shape for consumers
(verified equal to the previous table).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(mobile): derive the agent order, labels, and picker from src/shared
The mobile mirror (and its regex-over-desktop-source parity test) predates
mobile importing runtime values from src/shared, which it now does in a dozen
modules. Only the favicon-domain map stays mobile-local because desktop's lives
in the renderer catalog next to bundled ?url imports. The parity test now
imports the real registries and also checks label parity.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(web): align preload surface after hook IPC removal
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Review follow-ups on the create-field shaping:
- Seed only `reporter`. isVisibleJiraCreateField matches every required
non-system field, so the previous filter also pre-filled required custom
user pickers (Reviewer, Requested by) that Jira never defaults.
- Skip the seed when the target project is on another site. The viewer
comes from the active site, and the host shapes against the target's
client, so a foreign accountId is rejected on Cloud and can silently
resolve to a different person by username on Server/DC.
- Render JiraUserPicker only for scalar user fields. It holds one user, so
an array-of-user field collapsed to a single member; those keep the
existing comma-separated text path, which still reaches toUserFieldValue's
array branch.
Adds docstrings across the touched Jira functions to satisfy the
docstring-coverage pre-merge check.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdMzQN6T3jRVahCK2sNyur
- Jira rejects a bare string for reporter/user-picker fields on issue
create, so shape customFields values into {accountId}/{name} objects
for keys the caller flags via userFieldKeys.
- Seed required user fields with the authenticated viewer by default
and add a searchable user picker (jira.searchUsers) so users aren't
forced into free text for reporter/custom user fields.
* fix(agents): keep OMP identity and forward ask/approval events (STA-4130)
A live OMP pane was re-owned as Pi because the generic pi-compatible
fallback always won, ask events blocked without a question payload, and
OMP suppressed tool_approval_* unless an extension registered handlers.
Mark Pi as the title-group fallback so a specific OMP identity is not
downgraded, publish OMP ask input as the existing questions envelope, and
forward tool_approval_requested/resolved onto blocked/working.
STA-4130
Related to #14278
Co-authored-by: devatnull <59279509+devatnull@users.noreply.github.com>
* fix(agents): keep launch Pi ownership over OMP wrapper frames (STA-4130)
The pi-compatible fallback treated every generic Pi owner as inferred, so an
explicit launch-Pi pane (and a launchless Pi pane with an OMP-shaped title)
was re-owned as OMP. Launch provenance now stays authoritative; only an
inferred status-frame owner yields to a specific sibling, and same-group
titles no longer count as reuse.
STA-4130
* fix(agents): drop Pi wrapper idle titles while OMP hook is active (STA-4130)
Title-completion suppression compared pick-a-winner ownership, so a Pi ready
frame looked like a different agent than a live OMP hook and fired a spurious
task-complete notification. Reuse checks now use the title-identity group.
STA-4130
* fix(agents): restore OMP approval forwarding after merge
* fix(agents): restore title-owner API after merge
* test(agents): update identity inventory ratchet
---------
Co-authored-by: devatnull <59279509+devatnull@users.noreply.github.com>
Co-authored-by: Merge Sim <sim@local>
* fix(omp): read Pi/OMP static state-title markers and retire stale spinners
OMP 17.2.12 replaced its animated braille title frames with static markers
on WSL/ConPTY (`π : working`, `π > idle`, `π ! needs input`). Orca read all
three as idle, so a working OMP pane lost its status, and a synthetic title
spinner started by an earlier hook kept rotating after its status row was
gone.
Classify the markers from one shared table so a later upstream punctuation
change is a row, not a reparse, and stop the spinner when the hook row it
stands in for is cleared or dismissed.
Fixes#13890
* test(omp): preserve static state titles during normalization
---------
Co-authored-by: Merge Sim <sim@local>
* feat(browser): edit a page annotation's comment and intent inline in the tray
Fixing a typo in a browser annotation required deleting it and re-picking
the element in the page. Each tray row now has a hover-revealed edit
button that swaps the row to an inline editor (comment textarea seeded
with the current text, intent toggle, Save/Cancel), reusing the compose
card's submit shortcut and length budget. Edits route through a new
updateBrowserPageAnnotation store action that no-ops on a missing id and
sanitizes the merged annotation exactly like the add path.
Escape inside the editor cancels only the row edit: the annotate mode's
window-level capture listener now exempts the edit container, since
stopPropagation from a descendant cannot reach a capture listener.
Editing is deliberately limited to comment and intent: element targets,
cross-navigation persistence, and the prompt builder are untouched (the
builder reads live off the array, so edited text flows through).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rEaTnSaSneEMEJvebzM53
* test(browser): pin the annotate-mode Escape exemption for annotation edits
The data-slot annotation-edit clause in useGrabMode's capture keydown
listener had no test that dispatched a keydown through it, so deleting
the clause left the suite green. These tests invoke the real registered
capture handler with an Escape whose target sits inside (and outside) an
annotation-edit container; the inside case uses a button, which
isEditableKeyboardTarget does not cover, so only the exemption clause
can keep grab mode alive. Removing the clause now fails the suite.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Show remote host failure details in submenu
* chore(i18n): sync catalog for remote host submenu strings
* fix(status-bar): keep remote host actions accessible
* fix(status-bar): preserve submenu keyboard navigation
* fix(status-bar): drop the submenu chevron on remote host rows
The panel sits at the screen edge, so Radix collision-flips the submenu to the
left. A right-pointing chevron then points away from where the menu opens.
hideChevron is opt-in, so the 21 other SubTrigger consumers are unchanged.
---------
Co-authored-by: Merge Sim <sim@local>
* fix(agent-hooks): route reminted pane keys to canonical identity (STA-3993)
Spawn was stripping $$<base32>:L$$ ORCA_PANE_KEY values (and the launch
token) instead of rewriting them to the metadata-proven tab:leaf key, so
OMP hooks never entered last-status.json and sleeping rows stayed working.
Alias that exact remint form onto the canonical pane so later posts still
route, and keep unmatched tokens from stamping another pane.
* fix(agent-hooks): keep reminted pane-key aliases first-pane-wins
Remint tokens have no embedded tab identity, so a later spawn that reused
the same $$ token with a different tab/leaf was overwriting the alias and
routing leftover hook posts onto the new pane. Refuse destination changes
for that form while still allowing same-pane pty id updates.
* fix(agent-hooks): keep pane alias limit import valid after refactor
* fix(agent-hooks): bound pane alias destination keys
* fix(ssh): keep pane identity env stripped when hooks disabled
---------
Co-authored-by: Merge Sim <sim@local>