Commit Graph
10019 Commits
Author SHA1 Message Date
Jinjing 0ac68f0d4b Add background color to search button in sidebar (#16130)
Applies a subtle background color to the search button to make it more visually distinct.
2026-08-24 22:42:11 -07:00
Jinjing 90743cf17b feat: add workspace search and improve installation target UI (#16380)
- Add searchable Combobox for workspace selection with label and type filtering
- Redesign agent picker from collapsible to popover with better visual hierarchy
- Restructure skill install review screens with card-based sections
- Add comprehensive tests for workspace search and agent selection
2026-08-24 22:41:06 -07:00
Neil a7505fd911 fix(cli): spawn a version-manager CLI with its own node runtime (#16365)
* fix(cli): spawn a version-manager CLI with its own node runtime

resolveCliCommand falls back to scanning every version-manager install when
PATH misses, so it can hand back ~/.nvm/versions/node/v20.x/bin/codex while
PATH still leads with v22. Nothing paired the binary with the runtime it was
installed against, so its `#!/usr/bin/env node` shebang loaded a v20-built
native module under a v22 ABI and the agent died on first require (#10932).

Reproduced with a real addon rather than asserted: a CLI requiring a
cpu-features build for NODE_MODULE_VERSION 115, spawned with v24 leading
PATH, fails with ERR_DLOPEN_FAILED and exit 1. With the CLI's own bin
directory prepended it runs clean.

withCliRuntimeOnPath prepends the resolved command's directory when that
directory ships a sibling node, and is a no-op otherwise — so a Homebrew or
/usr/local CLI is untouched, and the WSL paths pass a bare `codex`/`claude`
that is not absolute and so never matches.

Host CLI resolution in the Claude login path is now lazy, keeping the WSL
branch from resolving a host binary it never spawns.

* fix(cli): split PATH on the delimiter we join with, pair app-server too

Readiness review findings, all four addressed.

withCliRuntimeOnPath chose its join delimiter from the platform option but
split with the host's. Passing platform:'win32' from a posix host turned
`C:\Windows;C:\Windows\System32` into `C;\Windows;C;\Windows\System32` —
every drive letter torn off at its colon. Latent, since no shipped caller
passes platform, but the sole win32 test was written against the corrupted
value and asserted one split segment, so it green-lit the shredding.

That test's other assertion was vacuous: it seeded only `Path`, so the
`PATH` key it asserted absent could never exist. Deleting the whole
case-dedupe block left the suite green. It now seeds both keys and asserts
the full joined string; removing the block fails it.

Nothing covered the wiring, and the argument choice is the easy thing to get
silently wrong. Note it only diverges on win32 — on posix
getSpawnArgsForWindows returns the CLI itself, so pairing the spawn command
is indistinguishable there. The new test drives the win32 branch with a .cmd
fixture; pairing spawnCmd or dropping the wrapper both fail it now.

codex-trust-grant-host and codex-session-index-heal spawn the same
`codex app-server` subcommand through runCodexAppServerSession and were left
unpaired. Pair centrally there via a new optional cliPath, since
invocation.command may be a cmd.exe wrapper.

Pairing tests live in their own file: adding them inline pushed
codex-fetcher.test.ts past the 800-line ratchet.

* fix(cli): read the Windows path key the child will actually use

Round-2 review finding. The read was narrower than the delete: the key was
picked from exactly two spellings (`Path`, else `PATH`), while the twin
dedupe removed every key whose lowercase form is `path`. A block spelling it
`path` or `pATh` therefore had its value deleted without ever being read,
handing the child a PATH containing only the CLI's own directory — a strictly
worse outcome than not pairing at all.

Win32 resolves env names case-insensitively and object order preserves block
order, so the entry the child reads is the first case-insensitive match. The
repo already encodes that rule in resolvePathEnvKey
(src/main/pty/windows-path-segment-merge.ts); src/shared cannot import from
src/main, so mirror it locally.

Verified by execution across six env shapes: lowercase, mixed-case, Path-only,
PATH-only, both twins, and a PATHEXT control that must not be touched. All
preserve the original PATH; before the fix the first two lost it entirely.
Reverting the selector fails the new test and nothing else.
2026-08-24 22:30:04 -07:00
NeilandSeongho.Bak 4a57cfac9a fix(terminal): give OMP Pi's Windows Shift+Enter encoding (#16376)
OMP wraps Pi's TUI, so Shift+Enter bytes land in a Pi reader that decodes
CSI-u. The omp profile had no `windowsShiftEnterEncoding`, so it fell back
to Esc+CR — which submits instead of inserting a newline (#9703).

This was latent while an OMP pane's stored title could read either "Pi" or
"OMP" depending on which interleaved frame committed first. Pinning the
title to the launch owner (#16373) made it deterministically "OMP", so the
Windows Shift+Enter fallback now always resolves `omp` and always picks the
wrong encoding.

`prime-agent` already carries this entry for the identical reason.

Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>
2026-08-24 22:27:45 -07:00
Neil e217fdd10f build(orcad): gate orcad's own graph, and prove it loads under plain Node (#16368)
* fix(orcad): close the browser-provider gaps

The providers landed without enforced coverage, so a regression in either path
would have landed silently.

- CI: the external-Chromium integration test was gated on ORCA_BROWSER_EXECUTABLE
  and nothing ever set it, so it skipped forever. It now runs in its own job
  against the runner's Chrome and FAILS when Chrome is absent rather than
  skipping, because an unset variable is exactly how it went uncovered. Timeout
  raised to 120s: a warm run is ~7s but the first launch against an unseeded
  profile took 30s and hit Vitest's default, and CI is always that cold case.
- Electron provider had no test at all. It is the path anyone with the desktop
  app hits.
- Browser unavailability reported one message for four causes, including telling
  an operator to set a variable they had already set.

Fixes a live defect found while covering it: the runtime advertises
browser.tabCreate.known-id.v1 unconditionally, so a web client sends a
provisional page id for a page that does not exist yet — and the sidecar's
generic requestedPageId branch ran require() on it first and threw. Every
known-id create against the Electron provider failed. The adoption logic was
already there; only the ordering was wrong.

Also updates the workflow-parallelism guard, which correctly caught the new job
missing from verify's required-check list, and asserts verify actually reads it.

* build(orcad): gate orcad's own graph, and prove it loads under plain Node

Two gaps the artifact's own comment asked for.

The ratchet measured only orca-runtime + runtime-rpc, but orcad imports ipc/pty
directly to install the PTY controller, so its graph is strictly larger. The gate
could read zero while the shipped artifact regressed. orcad's entry is now a
ratchet entry point, and the baseline stays empty with it included.

orcad cannot join plain-node-entry-guard — that is a rollup plugin keyed on
electron-vite input names, and orcad is an esbuild artifact. But the half that
matters here is the guard's smoke-load: scanning the metafile proves no module
NAMES electron, not that the graph resolves under plain Node. A dynamic require,
a missing native or a top-level throw all pass the scan and fail at runtime.
build-orcad now runs the bundle with a bogus flag and requires the argv rejection
that only a fully loaded graph can produce.

Verified: a bundle that builds but throws on load fails the gate.
2026-08-24 22:21:26 -07:00
NeilandSeongho.Bak 3b6eb03349 fix(terminal): stop OMP tab title flapping between OMP and Pi (#16373)
* fix(terminal): stop OMP tab title flapping between OMP and Pi

OMP wraps Pi, and both share the `pi-compatible` title-identity group. Two
writers publish frames for the same pane under different labels: main's
synthetic spinner injects "<frame> OMP" every 80ms, while the wrapped Pi
harness emits its own "Pi" frames.

`isDecorativeAgentTitleFrameChange` keys on `status:textWithoutSpinner`, so
`working:OMP` and `working:Pi` read as meaningful changes. The alternation
defeated spinner-churn suppression entirely: every 80ms frame committed a
store patch plus a runtime-graph sync, on both the tab-title and
runtime-pane-title paths.

Pin same-group identity frames to the tab's launch owner at both store
choke points, reusing the existing owner-normalization helper already
applied on the sidebar, remote-sync, and mounted-pane paths.

The relabel is scoped to bare identity frames ("⠋ Pi", "Pi ready"); a
semantic session title ("π - <session> - <cwd>") carries text no agent
profile can reproduce and is left untouched, so this does not reintroduce
the generic-label complaint in #16093.

* fix(terminal): scope owner relabel to cross-identity frames

A frame that already names the tab's own agent carries authoritative status
wording, so relabeling it restated bare "Pi" as "Pi ready" and changed a
Pi-owned tab that never flapped. Only relabel when the frame names a
different member of the identity group.

Also fixes the repro suite's types against the project typecheck.

Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>

---------

Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>
2026-08-24 22:16:22 -07:00
Neil f7033e0e70 build(windows): drop the packaged node-pty prebuild that can silently replace the patch (#16350)
* build(windows): drop the packaged node-pty prebuild that can silently replace the patch

node-pty's loader tries build/Release, then build/Debug, then
prebuilds/<platform>-<arch>, and swallows every failure in between. Windows
packaging ships both the source build and the prebuild, and only the source
build carries Orca's job-object exports (listJobProcessIds, terminateJob,
assignCurrentProcessToJob).

So an ABI mismatch, a truncated file, or an AV quarantine of
build/Release/conpty.node degrades the shipped app to the UNPATCHED prebuild:
PTY teardown silently falls back to guessing by PID ancestry, with no error
anywhere. That is the failure mode that made #16059 hard to see -- an install
that looks fine and quietly cannot own a PTY tree.

Removing the fallback turns a silent downgrade into a loud load failure.

Scoped narrowly: only win32, and only when the source build is actually
present, so a build that legitimately has no build/Release keeps something
loadable. macOS and Linux prebuilds are untouched -- they have no patched
export to lose.

Refs #16059.

* fix: delete only the stale conpty fallback, not the whole prebuilds tree

Review caught a P0 in the first version of this change, and it was the same
defect the PR exists to prevent, pointed at a different target.

Orca's own patch removes the `conpty_console_list` and winpty `pty` gyp
targets, so a Windows source build emits conpty.node and nothing else.
conpty_console_list.node, pty.node, winpty.dll and winpty-agent.exe therefore
exist ONLY in prebuilds/. Deleting the tree removed them:

- the forked console-list agent throws at require, and its caller resolves null
  with silent: true, so console-membership probing dies with no log anywhere --
  a new silent degradation, in a PR whose thesis is "make it loud";
- node-pty still selects winpty below Windows build 18309, so PTY spawn would
  fail outright on Server 2019 / Win10 LTSC 2019.

Now removes only prebuilds/win32-<arch>/conpty{.node,.pdb}, and only when
electronArch matches the host arch -- a cross-arch package copies the host's
build/Release, so its presence does not mean it matches the target, and
deleting the target-arch prebuild would remove the only loadable binary.

The old fixture wrote just conpty.node, so it could not see any of this. It now
seeds a realistic prebuilds directory, and four tests assert each sibling
survives; all four fail against the broad delete.

Credit: review counsel.
2026-08-24 21:43:22 -07:00
Neil 8043b5f705 Refactor worktrees IPC into cohesive modules (#16190)
* refactor worktrees IPC into cohesive modules

* test update PTY waiver invariant paths

* revert unrelated oxfmt reflow from merge commit
2026-08-24 21:19:11 -07:00
Neil d8807801ff Refactor GitHub Project surfaces and mutations (#16166)
* refactor github project modules

* restore dropped issue #4756 guard comments
2026-08-24 21:18:55 -07:00
Neil 087b895524 refactor(daemon): split oversized PTY services (#16160)
* refactor(daemon): split oversized PTY services

* revert(daemon): restore merge-base session listing and canceled-spawn behavior

Two behavior changes rode along with the file-splitting refactor:

- listLiveTerminalHostSessions dropped sessions with isTerminating, not just
  dead ones, hiding sessions the merge base still advertised.
- spawnAndPublishSession called session.beginTermination() before publishing a
  canceled spawn into the host map.

Both hunks are reverted to the merge base; the refactor is untouched.
2026-08-24 21:18:11 -07:00
Jinwoo Hong 4da8848168 fix(mobile): preserve relay close recovery codes (#16293) 2026-08-24 21:13:38 -07:00
Neil 09048c63d4 feat(orcad): add headless browser providers (#16193)
* feat(orcad): add headless browser providers

* fix(orcad): merge the duplicate runtime-browser type import
2026-08-24 21:11:45 -07:00
Neil 788575e300 fix(crash-reporting): stop destroying user crash notes (#15252) 2026-08-24 21:03:34 -07:00
Neil 5869e6d39e refactor Linear workspace surfaces (#16332)
* refactor Linear workspace surfaces

* refactor(linear): restore merge-base behavior in split modules

The Linear surface split smuggled in three behavior changes; revert them
so the refactor is a pure move.

- detail-state: drop 'project' from EDITED_LINEAR_ISSUE_FIELDS. List
  issues never carry `project` (only getIssue maps it), so preserving it
  across hydration permanently blanked the hydrated project whenever an
  edit landed while linearGetIssue was in flight.
- detail-state: handleProjectChanged no longer sets hasEditedRef.
- project-selector: remove the mountedRef/requestId guards around the
  global patchLinearIssue write and the success/error toasts.
- sub-issues: remove the added isComposing guard on the title Enter key.

The detail-state test asserted the smuggled project-preservation; updated
to assert hydration owns `project`.
2026-08-24 20:57:53 -07:00
Neil ec12a0e442 refactor settings maintenance modules (#16169)
* refactor settings maintenance modules

* revert behavior changes smuggled into settings split

- hoist isAdvancedOpen state back into RepositoryHooksSection so it survives
  SearchableSetting unmount during settings search
- drop isComposing guards absent from the merge-base AgentsPane handlers
- restore merge-base JSX for the 'when one exists.' fragment (no separator)
2026-08-24 20:57:37 -07:00
Neil d74f469379 refactor(feature-wall): split animated browser and terminal stories (#16142)
* refactor feature wall animated visuals

* fix(feature-wall): restore merge-base render behavior in split visuals

- Hoist workbench reduced-motion state to module constants so cursorTarget
  identity is stable and the cursor layout effect stops re-firing per render.
- Render one frame component and branch on the state source so toggling
  reducedMotion re-renders the storyboard instead of remounting its DOM.
2026-08-24 20:57:21 -07:00
Neil b6a24ecac1 refactor: split GitHub backend modules (#16194) 2026-08-24 20:46:07 -07:00
Neil 9bcaf09869 refactor loading store into cohesive domains (#16192) 2026-08-24 20:45:53 -07:00
Neil b516300b8c refactor agent hook listener modules (#16187) 2026-08-24 20:45:38 -07:00
Neil 2c77f71c4f refactor(renderer): split IPC event bridges (#16185)
* refactor(renderer): split IPC event bridges

* test(renderer): follow extracted IPC shortcut bridge
2026-08-24 20:45:23 -07:00
Neil f57114a106 refactor(renderer): split terminal store slice (#16184) 2026-08-24 20:45:08 -07:00
Neil d3c37b8e40 refactor(renderer): split web preload API (#16181) 2026-08-24 20:44:53 -07:00
Neil 07b13c8468 refactor git repository host boundaries (#16177) 2026-08-24 20:44:34 -07:00
Neil 2960fe9193 Split relay entrypoints into focused modules (#16151) 2026-08-24 19:55:57 -07:00
Neil 1c436a8084 refactor(rpc): split terminal methods into cohesive modules (#16188) 2026-08-24 19:52:12 -07:00
Neil 8776b6e49a refactor(renderer): split GitHub store slice (#16183) 2026-08-24 19:52:04 -07:00
Neil 4371aaf722 refactor provider clients into domain modules (#16168) 2026-08-24 19:51:57 -07:00
Neil 0b66daffcc refactor(cli): split orchestration handlers (#16139) 2026-08-24 19:51:40 -07:00
Neil 5c116b6ec2 fix(startup): stop the PATH seed pinning nvm to its newest install (#16314)
* fix(startup): stop the PATH seed pinning nvm to its newest install

patchPackagedProcessPath prepends the newest nvm version dir to
process.env.PATH, then hydrateShellPath probes the login shell with that
same env. nvm's startup `use` honors whatever node is already on PATH
instead of the user's `default` alias, so the probe returns a PATH pinned
to the newest install and every terminal pane inherits it.

A user whose newest nvm node is a bare install then loses every global CLI
(codex, claude, gemini, vercel...) inside Orca while they still resolve in
Ghostty/Terminal, which start from the bare GUI PATH and fall through to
`default`.

Probe with the PATH the process launched with. Windows already gets this
through WindowsShellPathOwnership.

* test(startup): pin the platform in the probe-env test

shellProbeEnv short-circuits on win32, so the POSIX-only assertion failed
for anyone running the suite on Windows. Matches the convention in
hydrate-shell-path.windows.test.ts.

Also narrows the win32 exemption comment: WindowsShellPathOwnership
snapshots its baseline after the seeds land, so it does not unwind them.
The exemption holds because Windows keys PATH as `Path` and no Windows
seed pins a node version.

* fix(startup): give win32 the same probe insulation, keyed by Path

The previous commit exempted win32 on the stated grounds that no Windows
seed pins a node version. That is wrong: getVersionManagerDirectories
calls getNvmVersionDirectories on every platform, so a Git Bash user whose
nvm uses the POSIX ~/.nvm/versions/node layout gets the newest version dir
seeded on Windows too, and the -ilc Git Bash probe inherits it.

Record the PATH key alongside the value and overwrite that entry in place,
so Windows never carries both `Path` and `PATH` — which was the only real
reason to skip win32.

* fix(startup): snapshot the launch PATH at module load, log probe failures

Three loose ends from the review, folded in rather than deferred.

The probe's clean PATH was handed over by an explicit recordLaunchPath call
from the seeding site, so the invariant lived across three files and a
refactor that moved the seed call would silently re-pin nvm. Snapshot PATH
during module init instead: that runs while the import graph is evaluated,
strictly before any statement in main's body, so it cannot observe the
seeds and there is no call ordering left to break. All seven importers are
static, so no lazy import can defeat it. A test asserts the probe ignores
later process.env mutation, and fails if the live read is reintroduced.

A failed startup probe leaves the seeded newest-nvm dir in front and said
nothing, so the population whose rc files blow the 5s budget hit the
original symptom with no diagnosable trace. Log the failureReason.

The probe is an interactive login shell, so rc files that exec into a
multiplexer or start a heavy prompt can outrun that budget with no way to
opt out. Set ORCA_SHELL_PATH_PROBE=1 so they can take a fast path.

* fix(startup): drop the other-cased PATH key from the Windows probe env

Caught by running the suite on a real Windows machine, not a mocked
platform. The spread of process.env is a plain, case-sensitive object,
while Windows resolves env names case-insensitively. Writing the captured
`Path` back onto it left the seeded value still live under `PATH`, so the
probe shell could read either one — the exact duplicate-key hazard the
win32 branch was supposed to prevent.

Drop any other-cased variant of the key before writing.

* fix(startup): preserve launch PATH across app restarts
2026-08-24 19:45:47 -07:00
Neil 0bfd3808f6 test(pty): pin the renderer-liveness guard STA-2373 relies on (STA-5373) (#16345)
* test(pty): pin the renderer-liveness guard STA-2373 relies on (STA-5373)

STA-5373 reported that #15927 dropped the `webContents.isDestroyed()` half of
the guard #10065 added for STA-2373, and that the app therefore dies when a
daemon death fans out to every pane. The guard is present on main: #15172
restored it at both senders when it split the PTY monolith, and a per-file
count across v1.4.188..HEAD shows only relocation (pty.ts:4 ->
write-input.ts:2 + bind-listeners.ts:2). The reported 4 -> 0 was scoped to
`src/main/ipc/pty.ts`, now a 39-line barrel. No production fix is needed.

What was real is that nothing pinned the guard — it survived #15927 only
because #15172 happened to re-expand it — and the shared PTY test fake made
that invisible: its `webContents` had no `isDestroyed` at all, so both guards
passed vacuously in every suite. That is also why they are written defensively
as `typeof ... === 'function' && ...`.

- Give the fake an `isDestroyed` mock, re-stubbed to `false` each beforeEach
  rather than left `undefined`, so "alive" is stated rather than accidental.
- Cover both senders red/green: the daemon-death fan-out
  (bind-listeners.ts:37) and the per-write reporter (write-input.ts:57).
  Verified red against a window-only guard and green with the real one.

The per-write case needs a chunked write. A single-chunk write is already
fenced by `isPtyWriteEventFromMainWindow`, which rejects the event once the
WebContents is gone; only the multi-chunk path yields a macrotask mid-write,
letting the renderer die after the sender check passes. That is the sole route
to this sender with a dead WebContents, and what makes its own guard
load-bearing.

Also deletes `src/main/ipc/pty-renderer-surface.ts`: zero importers repo-wide,
and its `isRendererGone` is exactly the weakened predicate. Its comment claims
the headless path "now passes null", but register-headless-runtime.ts:26 still
fakes `{ isDestroyed: () => true }` — #15172 rolled that back too. Adopting it
would reintroduce STA-5373 for real.

Verified: 17 tests across the two pty write suites; full src/main/ipc run 3156
pass (4 pre-existing @parcel/watcher failures in filesystem-watcher-real and
worktree-base-directory-poller, unrelated and failing identically on a
pristine tree); pnpm typecheck clean; oxlint clean.

* docs(pty): correct headless-path comments the #15172 rebase left stale

#15927 made `registerPtyHandlers` accept `BrowserWindow | null` so the headless
path could pass null instead of faking a window. #15172 reverted that signature
while splitting the PTY monolith, but the comments describing it survived, so
three of them now document an API that does not exist.

- orcad-entry.ts: the module docstring claimed orcad installs its controller via
  `registerPtyHandlers(null, …)`. It uses `registerHeadlessPtyRuntime`, and null
  is not accepted. It also claimed desktop surfaces are "declared rather than
  faked" — true except for the renderer window, which is still faked.
- register-headless-runtime.ts: record why the fake is safe rather than leaving
  `isDestroyed: () => true` looking arbitrary. It is load-bearing: every
  renderer-liveness guard reads it and skips, so no send is attempted.

Also gives the fake a `webContents.isDestroyed`. The real guards check both, and
a missing method reads as "alive" — the same gap this PR fixes in the test fake.
No behavior change: the window-level check already short-circuits.

Verified: 45 tests across the liveness-guard, startup-barrier, management and
kill/exit suites; pnpm typecheck clean; oxlint clean.
2026-08-24 19:33:35 -07:00
Jinwoo Hong c60d2ba895 fix(agent-resume): stop ghost resume tabs after finished turns (#16308) 2026-08-24 19:32:52 -07:00
Jinjing cc4801320a fix(terminal): stop stale multiplex stream handles from swallowing input (#16325)
* test(terminal): pin park/reveal re-subscribe on a shared multiplexer

Investigating STA-5098. Parking a mirrored remote tab closes its stream
while a sibling tab keeps the multiplexer alive, so the reveal
re-subscribes on an instance that already retired a stream.

This came back green, which exonerates the multiplexer as the cause of
STA-5098 — the wedge is above it. Kept as a contract guard; the header
says explicitly that it is not coverage for that ticket.

* fix(terminal): stop stale multiplex stream handles from swallowing input

A stream handle whose record was dropped (park close, or a reconnect that
clears the stream table) kept reporting success: sendFrame gates only on
socket readiness, never on stream membership. The host drops those frames
for an unknown stream id, so a revealed cold-parked remote pane looked
connected while the PTY never saw a byte and never painted (STA-5098).

Reporting success also defeated the transport's own recovery — it re-sends
input over terminal.send when the stream refuses it, which never ran.

Guard the three public senders on stream membership, the check close() and
setOutputPaused already use, and drain input queued behind a viewport claim
on every stream install rather than only a still-pending claim.

Withdraws the parked-reveal re-subscribe test: its fake host answered
Subscribe with an immediate snapshot, so it could not fail.

* chore(terminal): tighten the stale-stream comments
2026-08-24 18:24:32 -07:00
OrcaWinandm4air 1a7934c54f Fix deleted remote worktree reappearing due to host ID mismatch (#16039)
* Fix deleted remote worktree reappearing due to host ID mismatch

Paired clients and servers may use different spellings for execution hosts
(e.g., client 'runtime:env-1' vs server 'local'). Resolve these spellings
before worktree.rm and worktree.forceDeleteBranch to prevent failures and
orphaned worktrees.

* Validate runtime kind before comparing environment IDs

Ensure parsed host IDs are actually runtime environments before
accessing their environmentId property. Fixes incorrect host ID
matching during worktree cleanup that caused deleted remote
worktrees to reappear.

* Use hostId directly when same-ID surviving host exists

When a surviving host has the same ID as the deleted worktree's
original host, use the hostId directly instead of qualifying it
through the runtime call host. This prevents worktrees from
reappearing due to host ID mismatch.

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-08-24 18:21:22 -07:00
Neil 50438041a8 fix(rate-limits): safely surface Codex RPC exit reasons (#16023) 2026-08-24 18:19:48 -07:00
Neil c83499fc8c Keep sidebar position when deleting active worktree (#16040) 2026-08-24 18:14:12 -07:00
Brennan Benson 31562c5b27 fix(windows): attach interactive login children to console input
Verified on native Windows awin at the exact PR head with Electron CDP/Playwright: the Claude sign-in console is visible, cancellation after console launch restores Add Account state, and the login process/PID/temp cleanup completes.
2026-08-24 18:12:42 -07:00
Neil 7e7947665c fix(relay): gate relay control work on proven broker liveness (#16021) 2026-08-24 18:11:56 -07:00
Neil 701b957bc1 perf(renderer): narrow appearance store subscriptions (#16322) 2026-08-24 18:06:18 -07:00
Brennan Benson b55836d0c4 fix(popover): use non-passive wheel listener (#16309)
* fix(popover): use non-passive wheel listener

* fix(popover): preserve wheel handler cancellation and ref cleanup

* fix(popover): bridge descendant wheel cancellation
2026-08-24 17:14:20 -07:00
Jinwoo Hong fba910f2ea fix(crash-reporting): scope renderer crash evidence (#16313) 2026-08-24 17:01:18 -07:00
Brennan Benson 56f00afeca fix(sidebar): stop reporting an interrupted agent as done (STA-5357) (#16312)
* fix(sidebar): stop reporting an interrupted agent as done (STA-5357)

An interrupted turn rendered on the worktree card and the tab glyph as `done` —
visually identical to a clean completion. A cancelled or dead turn read as
finished work, and with several agents it contributed no signal at all, so a
sibling that merely finished could hide it entirely.

`interrupted` is a flag clamped onto `done` at parse time, never its own state,
so the activity summary never even received it: its Pick was
{state, workingMode}, and the `done` branch swallowed it into hasLiveDone.

Adds the flag end to end — summary, both activity hooks, the resolver, the card
glyph and the tab badge — and slots it below permission and above working, which
is where SUMMARY_STATE_ORDER already ranked it for the text summary. The card and
the expanded agent list now agree.

The tab-bar test that asserted the old behavior is flipped rather than deleted:
it explicitly pinned `done` to mirror the card, and that premise is gone.

* fix(sidebar): rank interrupted below every live state

Corrects the precedence from the previous commit. Interrupted means the user
pressed Esc or Ctrl+C — a deliberate act, so they already know. It must be
DISTINGUISHABLE from done (that is the bug) without being LOUDER than anything
live.

Moved below done in all three ladders — resolveWorktreeStatus, the tab attention
badge, and SUMMARY_STATE_ORDER, which had also ranked it above working. That
matches smart-attention, which already classes an interrupted done as 4 (idle),
below both done and working; the card, the tab, the summary text and the sort now
agree instead of two of them disagreeing.

Tests re-pinned to the corrected order, including one of my own from the previous
commit that asserted a finished sibling must not mask an interrupted pane — it
should, and now does.

* fix(status): preserve interrupted outcomes in aggregates
2026-08-24 16:43:57 -07:00
Brennan Benson 723158a519 feat(workspace-cleanup): blockers become labels, not refusals (#16282)
* wip(workspace-cleanup): PR 3 blockers-become-labels, recovered from a dead worker

Uncommitted work recovered from a worker that died with 'Agent process stop was
requested but never confirmed'. Committed as-is to preserve it; NOT verified yet.

* Fix workspace cleanup review regressions

* fix(workspace-cleanup): drop filter chips for the safety fields this PR removes

The cleanup dialog crashed on every open. My merge of #15300 brought in the
applied-filter chips, which read `safety.tiers` and `safety.selectableOnly` --
the exact fields this PR deletes. Electron QA caught it; the chip derivation runs
on open, so it threw before anything rendered.

Removed the two chip branches, their formatter entries, and the now-dead 'tier'
chip-kind label. The test that swept every chip keeps its breadth by using
`safety.dismissed`, which survives.

Worth noting what did not catch this: typecheck flagged only the test file, not
the source, because the QA agent had already patched the source locally without
committing. A merge that compiles can still remove a field a caller reads at
runtime, and only opening the dialog proved it.
2026-08-24 16:26:33 -07:00
Brennan Benson 4f525f17f5 feat(agent-status): add the pane agent identity resolver (#16157)
* feat(agent-status): add the pane agent identity resolver

Four ladders answer "which agent is in this pane" independently — the tab icon, the
open-tab/search occupant, the sidebar title rows, and the sidebar hook-row fallback — and they
disagree. Two consult the terminal title before the launch record, so a string Orca parsed
outranks a fact Orca owns.

resolvePaneAgentIdentity is the single ranked answer. Two rules, one of which is not an ordering:

1. Evidence is ranked by how directly it observes the process; a display title is last.
2. Each observation carries the runId of the agent run it describes. Evidence from a superseded
   run is INELIGIBLE, not merely outranked.

Rule 2 is the part reordering could never supply. A completed hook naming A plus a title naming
B is either a bug (hook right, title stale) or a legitimate pane reclaim (title right) —
identical signals, opposite correct answers. Run ids make them different facts: in the bug both
belong to the current run; in the reclaim the hook belongs to a previous one. That pair ships as
a test asserting the two produce opposite answers from the same evidence.

Missing run ids are treated as eligible. Absence means "this peer does not publish them", not
"this is stale", so an old host's rows are never blanked. Sibling evidence is opt-in so
pane-scoped consumers cannot inherit another pane's agent.

No consumer imports this yet; each migrates separately with its own evidence.

Verified non-vacuous: reversing the authority order fails 10 of 18 assertions and removing the
run filter fails 3.

* fix(agent-status): close three resolver contract holes found in review

**Duplicate evidence of one source resolved by array order.** `eligible.find(...)` returned the
first match, so two live hooks naming different agents were settled by input position — the exact
property this resolver exists to remove. The original order-independence test only used DISTINCT
sources, so it never exercised it. Conflicting same-class evidence now returns null with
`ambiguousAt`, and does NOT fall through to a weaker source: letting a title answer whenever two
hooks disagree is worse than saying nothing.

**A bare numeric runId collided across authority restarts.** `incarnation` is a total order only
within one `authorityId` (agent-status-observation.ts states this), and the id is regenerated per
authority instance, so a restarted host counting from its own floor would report `1` and match an
unrelated live run 1. The run key now carries its authority, and evidence from a DIFFERENT
authority is treated as incomparable — kept, like an absent key — rather than as stale.

**Title stayed reachable by consumers that authorize writes.** Ranking it last makes misuse
unlikely; `minimumSource` makes it impossible. An action consumer passes `'launch'` and weaker
evidence is dropped before ranking, so routing or delivery cannot name a target from a parsed
string even by reordering its inputs. Display surfaces omit it and are unaffected.

Also restores the generic agent-vocabulary parameter, which lives on the routing branch and was
lost when this branch was rebased.

Each fix is mutation-verified: first-match restored fails 3, ignoring authority fails 1, dropping
the floor fails 2. The authority test was itself vacuous on the first attempt — both sides used
`incarnation: 1`, so a resolver ignoring authority still passed on the numeric compare. It now uses
differing incarnations.

The remaining review finding, that `process > launch` has no freshness bound, is NOT fixed here:
it needs an observation timestamp the evidence type does not yet carry. Recorded rather than
silently dropped.
2026-08-24 16:06:13 -07:00
Neil beb1d45198 test(windows): run PTY IPC suites in Windows lane
Merged after clean CI, Windows PTY IPC validation, and readiness review.
2026-08-24 15:49:01 -07:00
Neil 2d500278b4 build(windows): refuse unpatched node-pty prebuilds
Merged after clean CI, Windows packaging verification, and readiness review.
2026-08-24 15:48:47 -07:00
Neil a856367db1 perf(renderer): gate runtime store projections (#16173) 2026-08-24 15:38:18 -07:00
Neil c139447934 perf(renderer): incrementally index terminal tab owners (#16172) 2026-08-24 15:37:57 -07:00
Neil a117bffb47 perf(renderer): project terminal topology consumers (#16171) 2026-08-24 15:37:17 -07:00
Jinjing 3bc13f7b8c Split monolithic PTY IPC module into organized submodules (#15172)
* rm unused files

* remove unused files

* Refactor PTY IPC and add host environment paths

- Split PTY handlers out of inline baseline checks
- Rename local PTY shell provider for clarity
- Pass userDataPath and resourcesPath to host environment

* Establish PTY daemon identity before first await in spawn flow

Move identity setup, session ID minting, and hidden delivery state to
the beginning of preflight, ensuring these complete synchronously
before any awaited operations. Defer async operations like folder
workspace validation; add liveness tracking for SSH provider failures.
Refactor pane spawn reservation to prevent concurrent spawns from
creating duplicate providers.

* Add incarnationId tracking throughout PTY exit lifecycle

Track PTY incarnation IDs in exit messages sent to renderer, and add cause tracking for exit events. This enables proper lifecycle state management when PTYs can be respawned or have multiple concurrent instances. Also adds deadline support to process listing operations and stop-request tracking for better shutdown observability.

* Use fake timers in SFTP namespace tests for deterministic abort handling

Tests now use `vi.useFakeTimers()` to control time during abort scenarios,
advancing timers explicitly instead of waiting on real async delays. Ensures
more reliable test execution without flakiness from timing-dependent behavior.

* Fix PTY spawn lifecycle: handle concurrent races and cleanup abandoned a

Properly release Agent Teams leader handles when spawns are abandoned or fail,
restore provisional PTY sizes on reattachment, and settle concurrent spawn races
for the same pane. Add validation guards for destroyed renderers and improve
handler re-registration to reset delivery state before bridging a new window.

* Move PTY cleanup to localized error boundaries

Restore provisional PTY size when build-options fails and guard pre-allocated handle registration. This ensures cleanup happens at the point of error, not deferred to the general catch block.

* Replace Promise.resolve() with vi.waitFor in PTY claim test

Wait explicitly for the providerSpawn call to be made using vi.waitFor()
instead of relying on event-loop yielding. This makes the test more
deterministic and reduces flakiness from timing assumptions.

* Redact PTY IDs in pending data drop diagnostics

Prevent workspace paths embedded in session IDs from leaking through
diagnostic logs by using redactPtyIdForDiagnostics.

* Mark PTY exit events as observed by provider

Exit handlers now receive `providerExitObserved: true` to
distinguish definitive provider-witnessed exits from inferred
state changes. Preserves optional exit cause when present.

* Add defensive input validation to PTY IPC handlers

Validate that IPC arguments are present and the correct type before
passing them to handler logic. Uses optional chaining and type checks
to safely handle malformed requests from the renderer process.

* Replace direct Electron imports with PTY host bindings

Abstract app, ipcMain, and powerMonitor access through getter functions
to support multiple host environments and improve testability.

* Defend against transient PTY setup failures with state cleanup

Host-env setup failures now trigger cleanup of runtime-allocated PTY state. Cached PTY geometry is preserved after transient reattach failures but cleared when the provider reports the PTY exited before the spawn reply—preventing stale geometry from corrupting future operations. Error handling now distinguishes expired SSH sessions and early-exit conditions to preserve geometry appropriately.
2026-08-24 15:30:16 -07:00
Jinjing b76a47e468 Add requireTuiAgentConfig to validate agent ids (#16310)
Agent ids persist in automations and settings, so they outlive the
build that wrote them. Direct config lookups fail with unclear
"Cannot read properties of undefined" when an id becomes unknown.
This function validates the agent and throws a clear error message
naming the unknown id.
2026-08-24 15:28:59 -07:00