mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
e4d95e032ddf13ff73be0813ef43ca2d136dbb03
9231
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e4d95e032d |
fix(windows): restore a CIM fallback for relay hosts with no native binding (#16550)
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
f72dcb908e | fix(contextual-tours): stop measuring 60 times a second while nothing moves (#16453) | ||
|
|
4ff428f763 | fix(mobile): retain relay during brief backgrounding (#16543) | ||
|
|
933345d347 |
Clarify upstream divergence stats for rebased branches (#16358)
* Clarify upstream divergence stats for rebased branches When a branch is rebased, it still tracks the pre-rebase upstream while comparing against the new base. Move upstream arrows to the head line to prevent them being confused with compare-base counts. * Show upstream divergence stats independent of compare base Measure HEAD against upstream regardless of compare-base state, so divergence indicators stay visible even when comparison is missing, loading, or failed. Also use cross-platform temp paths in tests. * Show commit counts against compare base, not upstream Upstream divergence (↑/↓ against tracking branch) was confusing for rebased branches — the counts appeared beside the base ref but measured against the upstream branch. Show only the compare base count instead, on the line that names it. * Report branch divergence in both directions Rebased branches are typically ahead AND behind their base; a single count hides this case. Use symmetric range with --left-right --count to capture both directions efficiently, then expose commitsBehind in the UI alongside commitsAhead. * Use semantic names for i18n keys and template variables Rename hash-based translation keys to descriptive identifiers and replace generic value0/value1 placeholders with semantic variable names like `count` and `ref`. Improves code maintainability and makes translation strings self-documenting. |
||
|
|
07b82340f3 |
Route terminal file links to sibling workspace tabs (#16544)
* fix: route terminal file links to sibling workspace tabs Detect when a clicked file is already open in a sibling workspace and route to that existing tab instead of creating a duplicate. Reorganizes workspace activation to dispatch by both worktree id and execution host, allowing the same worktree name across different remotes to be disambiguated and routed correctly. * test: validate terminal file link opens in correct sibling worktree Enhance test to check both file path and active worktree ID, ensuring the linked file opens in the intended sibling workspace. |
||
|
|
6a3bd2a1b8 |
fix: keep tab-cycle shortcuts in sync with rendered group order (#16549)
Tab-cycle shortcuts (Ctrl+Tab) were getting out of sync with what the TabBar actually renders. When a tab hydrated into the strip before group.tabOrder was updated, it fell out of the cycle until a click. Align keyboard cycling to use the same reconcileTabOrder pass the TabBar uses, so the cycle always walks what the user sees. Fixes STA-3475, particularly in remote servers where hydration timing diverges from local. |
||
|
|
b57b812e72 |
fix(ssh): recover initial state hydration
Hydrate SSH connection states independently of best-effort tombstone labels, with bounded fanout and regression coverage. |
||
|
|
5479bd9159 |
refactor(task-page): split task page into focused modules (#15163)
* rm unused files * rm unused files * fix(task-page): clean readiness lint findings * Add GitLab IPC timeout wrapper and improve error handling - Extract GitLab timeout logic into reusable `withGitLabIpcTimeout` wrapper to protect all GitLab API calls from hanging indefinitely - Apply timeout protection to all GitLab list and fetch operations - Add error handling for GitHub and Linear issue creation operations - Fix event bubbling in GitHub work item row to prevent nested button clicks from opening detail page - Remove unused `usePRReviewCellState` hook - Consolidate redundant imports * refactor(task-page): extract components and improve provider handling - Add glab timeout handling (30s) to prevent IPC thread blocking - Extract GitHub assignee/review components to dedicated files - Improve GitLab work item row keying (repoId:id) and keyboard event handling - Add context-aware error handling for Jira creation failures - Refactor GitHubAssigneeAvatar to use shared GitHubUserAvatar component * Add timeout support and error handling for GitLab operations - Admission control times out queued work after 30s to prevent indefinite queueing behind saturated operations - Mutation errors now display to users via toast instead of failing silently * Consolidate workspace attachment labeling into unified utility Extract common label-generation logic from GitHub and Linear work-item components into a single getWorktreeAttachmentLabel function, removing duplication across attachment types. * Improve TaskPage accessibility, i18n coverage, and error handling - Add missing aria-labels, roles, and semantic attributes for improved screen reader support - Extract hardcoded UI strings into i18n system with translate() calls - Add error handling and proper abort signal support for async operations - Use locale-aware date formatting throughout - Fix pagination disabled state and reviewer suggestion merging logic - Improve async state management with proper refs and effects - Add Textarea component import for Jira dialog * Improve TaskPage accessibility and i18n key naming - Add DialogTitle/Description with i18n to Linear issue dialog - Use useId to improve aria-labelledby in GitHub selectors - Replace hash-based i18n keys with semantic names - Use Object.hasOwn instead of `in` for safer filter checks - Fix PR review cell to clear input only on success * Add missing dependencies to TaskPage hooks and useCallback/useEffect arr Fixes exhaustive-deps warnings by adding missing setters, refs, and computed values to dependency arrays. Refactors GitHub and Linear issue state handling to compute values from pageData where available, with fallback to local state. Moves imperative ref updates into useEffect to properly track dependencies. * Fix TaskPage ref timing and null repo selection state Treat null newIssueRepoId as a valid selection, and use useLayoutEffect to synchronize the provider context ref before paint rather than after. * Extract Linear issue dialog components and fix popover scroll styling - Consolidate scroll styling: apply popover-scroll-content and scrollbar-sleek classes to PopoverContent wrappers - Remove redundant max-h-60 overflow-y-auto styles from inner picker divs - Fix GitHub new issue repo selection to explicitly target first selected repo on fresh mount - Correct CacheEntry import paths from store/slices/github to store/github/cache-model - Update tests to reference extracted dialog components instead of TaskPage.tsx * Improve GitHub task page i18n and fix issue creation edge cases - Add i18n support to GitHub work item aria-labels (draft PR, PR, issue) - Optimize work item row by extracting repeated source context call - Add safety check to prevent opening detail page when issue URL is missing - Fix dependency reference in detail opener hook - Extend GitLab job trace timeouts (60s backend, 65s frontend) for slow logs * Increase GitLab job trace fetch timeouts Job traces can outlive the runner's 30-second default timeout. Extend fetch operations to allow 60–65 seconds to complete. * Verify sourceContext variable extraction in github row test Update expectations to check that sourceContext is assigned to a variable rather than called inline, matching the refactored component implementation. |
||
|
|
290f192d84 |
fix(updater): surface and degrade renderer shutdown checkpoint failures (STA-5505) (#16497)
* fix(updater): surface and degrade renderer shutdown checkpoint failures The in-app updater could refuse to install with 'Renderer shutdown checkpoint was not completed.' while the actual persist() error was swallowed unlogged, leaving users stranded on old builds (STA-5505). - report the swallowed persist error: console, crash breadcrumb, and a cross-world DOM attribute so the thrown error (and the Update Error dialog) names the underlying cause - stop failing the checkpoint on sleeping-agent quit-capture errors; the periodic capture bounds the loss to one minute - extend the existing durable-session degradation to full-session staging failures during an intentional restart, preserving the dirty-draft guard * fix(quit): degrade and surface checkpoint-vetoed app quits (#15352) Cmd+Q walked the same shutdown checkpoint as the updater: a persist() throw preventDefault()ed the synthetic beforeunload and confirmNativeWindowClose returned silently — quit accepted, nothing logged, SIGKILL the only exit. - run the quit checkpoint inside a window-close scope so full-session staging failures degrade to the durable tier for app-level closes too (dirty editor drafts still hard-block) - when the checkpoint still vetoes the quit, toast the published failure reason instead of dying silently * fix(updater): retry-then-degrade staging and honest capture-loss accounting Review findings on the first pass: - a first full-session staging failure now stays a visible, retryable error; only a repeat failure degrades to durable-only staging, so a transient IPC failure keeps its retry instead of silently dropping just-captured scrollback - the sleeping-capture comment no longer overstates periodic coverage (periodic mode skips done panes and never stamps quit origin); the swallowed failure records a crash breadcrumb - pin the exact degradable-shutdown gate expression in the source-shape test so rewiring it cannot pass silently * fix(updater): arm the staging-retry flag only for degradable shutdowns An unrelated unload's staging failure must not burn the visible first retry of a later restart or quit. * fix(updater): isolate shutdown checkpoint retries Reset full-session staging retry state when a shutdown attempt is abandoned, and route Terminal-less closes through the same scoped synthetic checkpoint as mounted workspaces. Keep arbitrary thrown-value diagnostics non-throwing and localize the quit failure toast. * fix(updater): preserve checkpoint retry lifecycle * fix(updater): preserve empty checkpoint failure reason |
||
|
|
7c2b5a2334 | ci: rename cross-platform adhoc workflow (#16536) | ||
|
|
a1ec0479e2 |
fix(windows): revalidate PTY liveness from the job object, not a forked helper (#16419)
* fix(windows): answer console membership from the job object, not a forked helper
node-pty answers "which processes are attached to this pane's console?" by
FORKING a helper, because GetConsoleProcessList must run from a process
attached to that console. Orca asked on a foreground poll, per pane, so each
read spawned a conpty_console_list_agent -- hundreds of hidden processes
exhausting RAM within minutes, respawning as fast as they were killed (#10857).
QueryInformationJobObject has no console-attachment constraint: any process
holding the job handle can ask. Orca already creates that job per PTY, and
listPtyJobProcessIds has exposed it since the W1/W2 work with zero callers.
One syscall, no children.
Semantics the three call sites rely on are preserved: a root-only set still
proves the shell is alone (so a stale agent can be retired), and size > 1 still
proves something is running under it. The single difference is that a
descendant detached from the console stays in the job -- which widens the set,
the conservative direction for every caller.
Also fixes the third call site, which returned { available: false } whenever
membership was unavailable AND a recognized agent existed -- i.e. exactly while
an agent was running. Membership only ever narrowed the candidate list, so an
unavailable answer now leaves it unfiltered instead of failing the whole
resolution.
The no-fork test is asserted through a module-level vi.mock of
node:child_process. A vi.spyOn of a require()'d child_process does not
intercept the module's own import binding: the first version of that test
passed with a fork() deliberately reintroduced.
* fix(windows): keep console attachment for the candidate filter
Readiness review caught that this PR changed two different questions as if they
were one, and the repo's own plan doc had already said so:
"The job is the wrong set here -- it would re-admit precisely the detached
process the filter exists to drop." (windows-wsl-root-cause-plan.html, Use B)
The two uses:
- Use A, `size > 1` at local-pty-provider and the daemon tracker -- "is anything
in this pane besides the shell?". The job answers this, in-process and with no
fork. Unchanged from the previous commit.
- Use B, the candidate filter -- "which of these are ATTACHED TO THIS CONSOLE?".
Its whole job is dropping a descendant that detached, and the job object keeps
those, so answering it from the job makes the filter a no-op in its motivating
case: a detached `Start-Process droid` would be granted byte authority, and a
detached sibling would make an attached agent look ambiguous.
Use B goes back to GetConsoleProcessList, in its own module named for what it
answers, with its fail-closed null restored. That path is not the #10857 storm:
it runs only when a recognized agent candidate already exists, not on every
foreground poll. Bounding it to one pooled supervised helper is the remaining
half, and per the plan doc either half alone takes #10857 from unbounded to one.
My earlier claim that widening membership is "the conservative direction for
every caller" was wrong -- true for Use A, backwards for Use B. The hardware run
did not catch it because I measured a WSL pane, where the superset is harmless,
and never a detached GUI child, which is the divergence.
* fix: restore the coverage and ratchets the module split dropped
Round 2 of review. Two blockers, both from moving the forking code to a new
file without moving what guarded it.
- The child_process import ratchet was RED: windows-console-attached-processes.ts
imports node:child_process and was unlisted, and the old entry was stale. I
never ran that suite -- lint and the providers/daemon tests both pass without
it, which is exactly the gap the ratchet exists to close. Entry repointed;
count unchanged at 159.
- The forking module had ZERO tests. Its 11 assertions -- bounded timeout,
single kill, spawn error, malformed message, helper-pid removal -- were in the
file that now answers a different question, so the module that actually caused
#10857 was shipping untested. Moved with the code.
Also: nothing pinned the round-1 fix itself. No test drove console attachment to
null and asserted the fail-closed result, so re-deleting that branch would have
gone green. Now covered, and verified to fail when the branch is removed.
Cleanups the split left behind: `consoleMembershipUnavailable`/`consoleProcessIds`
renamed to `pane*` where they now hold job membership, the duplicated
`WindowsConptyMembershipDeps` type name, comments still describing the console
on the job path, and eight reliability-gate paths pointing at the moved tests.
* fix(windows): let a superset job answer expire instead of vetoing retirement
Round 3. The job read had reintroduced #9258's bug by a new mechanism.
`size > 1` returned unconditionally, so any pane holding a console-detached
descendant never retired its cached agent. A WSL pane always holds some: the
measurement in this PR's own test recorded job [40980,104068,4888,69908] against
console [69908,40980], i.e. console said "shell alone, retire" while the job said
"three others alive, keep". #9258's third commit describes the identical failure
from the other direction -- a bare shell reading as [helper, shell] "looked like
it still had a child ... the foreground refresh held the exited agent's identity
indefinitely" -- and that is what came back.
It bites because the read branch that serves the cached name across a Windows
shell fallback is deliberately untimed: #9258 made it so on the stated assumption
that "the background refresh authoritatively retires it". Removing the retire
authority left the identity with no bound at all. Second-order: a non-null cache
makes idleNoEvidenceShell false, which pins the refresh at the 1s TTL, so an idle
WSL pane also scanned the process table every second forever.
A TTL on the read would have been the wrong fix -- untimed is deliberate, because
on Windows the fallback name is structurally uninformative. Instead the job answer
is treated as what it is: a SUPERSET of the console, which cannot tell a working
agent from a leftover. Proof of absence retires immediately (size 1, unchanged);
an inconclusive answer ages out at 30s; unverifiable (null) still holds forever
per ssh-execution-boundary.md. Only successful scans that found no agent advance
the clock -- a degraded scan returns before this -- so the fix cannot expire an
agent it simply failed to see.
Also from review:
- Restore the root requirement the forked probe had. Without it a set of one
non-root pid -- shell gone, descendant alive -- read as "shell alone, retire",
inverting the truth.
- Rename to windows-pty-job-membership.ts / readWindowsPtyJobProcessIds. The old
name still said ConPTY console while reading the job, and conflating those two
sets is precisely the bug
|
||
|
|
5e5457983a |
Add clickable See more button to palette section overflow hints (#16533)
* feat: Add clickable See more button to palette section hints Allow incremental expansion of capped sections (worktrees, tabs, projects) by clicking "See more" to reveal 20 additional entries per section. Replaces static "X more" messages with interactive expansion that resets when the query changes. * Make soft preview See more non-clickable when no rows are hidden - The soft preview hint's expand button is only actionable when rows are hidden beyond the hard cap (leadingHardOverflowCount > 0) - When all rows already render, expanding would only reshuffle already-visible content without revealing anything new - Pass undefined as the handler to prevent the click behavior in this case - Add test case to verify the button doesn't appear when all rows fit |
||
|
|
5a8b4aff7b |
perf(git): only schedule the upstream-ref poll for the repo that has one (#16443)
* perf(git): only schedule the upstream-ref poll for the repo that has one A single global binding means at most one worktree holds a selected upstream ref at a time, so every other repo's 2s poll woke only to stat an empty set. Rebinding is synchronous and in-process, so reacting to it detects a newly selected ref exactly as fast as polling did — the wake-ups were pure waste. Measured at 100 repos with no selected ref: 0.811 -> 0.320 CPU-ms/s idle. * fix(git-watch): re-read ref selection after building the poller A rebind can flip back while the poller is being constructed. The concurrent unbind sees statusRefPolling still null and correctly does nothing, so without re-reading selection the in-flight build adopts a poller for a repo that no longer holds a ref — reinstating the idle wake-ups this change removes. * fix(git-watch): fence concurrent ref-poller starts with a generation token Startup and each rebind could be mid-build at once, and the startup path adopted its poller unconditionally. A rebind that won the slot first was then overwritten without being unsubscribed, stranding that poller's timer and visibility listener for the process lifetime. The generation names which attempt still owns the slot so every loser tears down what it built. Adds a regression test driving the real binding path; it fails if the poller is dropped without unsubscribing. Reported by CodeRabbit on #16443. |
||
|
|
91a500712c |
fix(crash-reporting): see the renderer memory the heap counters never report (#16449)
* fix(crash-reporting): see the renderer memory the heap counters never report Windows renderer crash 36048e26 arrived with 618MB of private renderer memory and a `renderer_memory` breadcrumb reporting a 150MB V8 heap. Both numbers were right: xterm scrollback lives in `Uint32Array` backing stores and glyph atlases live in GPU transfer buffers, and neither is counted by `usedHeapSize`, `mallocedMemory`, or Blink's allocator. That made the report unanalyzable. `renderer_memory_highwater` is the crumb carrying the subsystem census that names what grew, and it is armed on `usedHeapSize / heapSizeLimit`. At 150MB of a 4192MB limit that ratio is 3.6% — nowhere near the 60% mark — so the census never reached a single one of these reports. Measured on Windows (6 worktrees x 4 terminal tabs, 8000 lines each, this app at 4218d505): filling 24 mounted panes moved the renderer working set from 210MB to 656MB while `usedJSHeapSize` stayed at 43MB for the whole run. Sample the renderer's own OS footprint through `process.getProcessMemoryInfo()` (available in the sandboxed preload) and: - report `privateMB`, `residentMB`, and `outsideHeapMB` — the footprint minus everything V8 and Blink admit to holding — on every `renderer_memory` crumb; - arm the highwater census on private-footprint marks (600MB / 1000MB) as well as the heap ratio, so growth outside the JS heap now carries the pane and store census that names it. The footprint read is async, so a sample annotates with the previous read and refreshes in the background: one interval of staleness is irrelevant to a footprint trend, and awaiting it would make every sample reentrant. A shell without the bridge, or a runtime that withholds the read, keeps sampling exactly as before. Retained-breadcrumb keys now distinguish the two threshold ladders; keying only on `thresholdPct` collapsed every footprint crumb onto one slot. crash-diagnostics.ts split at the max-lines budget: memory sampling moves to renderer-memory-sampling.ts and the shared payload shaping to crash-breadcrumb-data.ts. * fix(crash-reporting): retain all renderer memory marks |
||
|
|
76f7e785fc | style(github): trim repo identity cache comments (#16517) | ||
|
|
630b71730b |
fix(sleep): restore agent auto-hibernation for non-Pi agents (#16430)
* fix(sleep): let non-Pi agents hibernate again, and stop repaints resetting the idle clock Auto-hibernation could never fire for claude, codex, gemini, opencode, grok, or any other resumable TUI agent — only pi/omp/prime-agent. #10238 broadened the `origin: 'live'` resume anchor so every resumable agent keeps its `--resume` handle when a turn ends. The planner rejects any pane that already has a sleeping record, and its exemption was still Pi-only. Since the planner's eligibility conditions are the same conditions that write the anchor, that rejection covered every otherwise-eligible non-Pi pane. - Split the conflated predicate. `isLiveResumeAnchorForCompletedAgent` answers "is this record just this pane's own live anchor?" with no vendor gate; the Pi-gated wrapper keeps today's exact semantics for the manual-sleep and quit capture call sites; `isAutomaticHibernationAllowed` carries the `automaticResumeBlockedBy` fence on its own. - Fence automatic hibernation. A fenced worker must not be auto-relaunched, and the capture does not copy the flag — so hibernating one would erase it. Checked in the planner and again inside the shutdown action against freshest state, re-evaluated after the synchronous capture callback that could itself fence it. - Anchor the idle clock on `stateStartedAt`, not `updatedAt`. Same-state repaints (OSC 9999, reconnect replays) advance `updatedAt`, restarting the 30-minute countdown and invalidating the two-tick confirmation. - Floor that anchor on PTY-binding age and a boundary-resolution stamp, so a wake or app restart still gets a full idle window instead of sleeping the whole backlog on the ancient timing main replays. The boundary stamp is written synchronously where the flag clears; sampling it per tick would miss a boundary written and cleared between two samples. - Signature drops `updatedAt` and gains agent kind plus full resume identity, which is the change detection `updatedAt` was providing by accident. Splits the planner into planner / pane-eligibility / snapshot to stay under the file length limit. * fix(sleep): drain pane teardowns sequentially `runAgentHibernationTick` launched every confirmed shutdown unawaited, so a backlog fanned all of them out at once. Each shutdown re-runs a full runtime-liveness sweep (one `terminal.list` per runtime-owned worktree, 10s timeout) and then a `terminal.stopExact` (15s timeout) — so ~100 overdue panes meant ~100 concurrent sweeps plus ~100 concurrent stops plus interleaved persistence writes. On an SSH runtime that is hundreds of near-simultaneous RPCs at the relay. The fanout predates this branch, but auto-hibernation could not fire for non-Pi agents, so it never ran at scale. Restoring eligibility is what exposes it. Awaiting each teardown also makes `tickInFlight` real: it was cleared in the `finally` as soon as the promises were launched, so it never covered the drains it was meant to guard. Each candidate still re-validates against a fresh plan at its own turn, so a slow drain cannot act on stale confirmation, and per-candidate failures are already caught so one stuck teardown cannot abort the rest. * perf(sleep): scope hibernation rechecks to pane owner |
||
|
|
67f2fc9e7c | perf(github): stop re-spawning the repo-identity probe every 30 seconds (#16450) | ||
|
|
cac5388545 | Update README downloads badge | ||
|
|
d2a35eebe3 | fix(mobile): avoid unsupported Hermes array sorting (#16506) | ||
|
|
c8567eb16e | fix(sidebar): preserve hidden rows in manual order (#16488) | ||
|
|
0e0a8c943b |
fix(mobile): recover Relay connections after resume (#16498)
* fix(mobile): recover relay sessions on resume * fix(mobile): expedite relay retry on app resume * fix(mobile): keep relay reconnect controller under lint limit * fix(mobile): rebuild relay client after pairing rejection * refactor(mobile): keep relay reconnect policy under lint limit |
||
|
|
efa3b972c2 | fix(native-chat): prevent duplicate mobile prompt echoes (#15656) | ||
|
|
29f1f4e545 |
test(perf): warm palette matcher before timing
Warm the palette matcher before measuring steady-state p95 performance. |
||
|
|
a9781a4118 |
STA-4150: client-hosted remote browser (consolidated) (#15448)
Co-authored-by: Jinwoo-H <jinwoo@stably.ai> |
||
|
|
98bdd653ab |
fix(native-chat): stop the spinner on a not-yet-flushed transcript (#16493)
* fix(native-chat): stop the spinner on a not-yet-flushed transcript A brand-new agent session can take minutes to write its first JSONL line, and one that is never prompted never writes it at all. The host emitted no stream frame until the file resolved, so every native-chat client sat on a bare spinner with the composer enabled but the transcript blank -- forever, in the never-prompted case. The resolve poll now reports the transcript as pending after a short grace, and both host handlers emit a `pending: true` snapshot. It is deliberately not a plain empty snapshot: an empty window sold as a settled read would capture over retained history and unblock consumers that require a trustworthy transcript (the launch-draft adoption would re-offer a prompt the agent may already have taken). Clients render it as the "start a chat" empty state while keeping the read unsettled -- `awaiting-transcript` on mobile, an `awaiting` read phase on desktop, which also stops the seed loop expiring into an error card for a session that is simply new. New optional field only, so older clients ignore it and still stop spinning. * fix(native-chat): negotiate pending transcript frames |
||
|
|
bc98655a39 |
fix(remote): stop an empty host inventory settling the mirror (#16414)
* fix(remote): stop an empty host inventory settling the mirror An inventory with zero published snapshots satisfied `settles.length === fullInventory.publishedSnapshotCount` as `0 === 0` and fired the environment-wide host-mirror verdict with no host evidence behind it. A live relay/SSH-paired host answers exactly that until its renderer's first publish, so the drained resume sweep forked a second agent onto a PTY the host was still running. Gate that one case behind a `terminal.list` readiness probe: it reads the PTY controller, not the session-tab mirror, so it sees live PTYs the mirror has not published. Only "no terminals" settles; live or unverifiable leaves waiters parked for the next inventory or per-worktree frame — a host with genuinely zero terminals still settles, so panes do not park forever. Fixes STA-5377 * fix(remote): fence host readiness probes by generation * fix(remote): preserve legacy terminal probe fallback |
||
|
|
61c7b51c8c |
docs(AGENTS): add code-reuse guidance and verification commands (#16451)
- Add "Reuse Before Reimplementing" section guiding developers to check for existing implementations before writing new code - Add "Verifying Changes" section with quick reference for typecheck, test, and lint commands - Fix typo: "Non-obviosu" → "Non-obvious" |
||
|
|
32df073e44 |
fix(browser): focus unified tab on browser page palette activation (#16366)
* fix(browser): focus unified tab on browser page palette activation When activating a browser page from the palette, find and focus the corresponding unified tab before setting active state. Ensures the tab group receives focus. Also increase e2e test timeouts to improve stability on slower runners. * test(e2e): read latest restored terminal frame * Fail browser page activation when unified tab is missing Without a unified tab, the workspace can't render in the pane. Reporting success leaves the previous tab on screen. Fail the activation to prevent this confusing state. |
||
|
|
e361da7fb7 |
Deleting skill (#16357)
* Add skill deletion with cross-platform transaction safety Implements end-to-end skill removal with placement enumeration, dependency guards, and transactional recovery. Covers native, WSL, and remote hosts; users can delete canonical directories and alias placements (symlinked directories or files) in a single atomic batch. Includes UI selection flow, preview, confirmation, and results band. Block reasons (bundled, plugin, unowned, stale) gate deletions that would fail or contradict user intent. * Organize IPC handlers into module subdirectories Move register-core-handlers and skill-delete-ipc-handlers into dedicated subdirectories for improved code organization and to reduce the flat structure in src/main/ipc/. * Make skill deletion recovery transactions idempotent Defer journal cleanup until both staging removal and receipt cleanup succeed, leaving the journal in place for startup to retry if either operation fails. This ensures the recovery process is safe to run multiple times without leaving partially-deleted skills. * Consolidate skill-delete files into dedicated module Reorganize skill deletion functionality into a modular structure under `src/main/skills/skill-delete/` with simplified file names. Remove the redundant `skill-delete-` prefix from file names since they now live in the dedicated directory. Update all import paths throughout the codebase to reflect the new structure, including imports from IPC handlers and RPC methods. * Fix broken import paths and add deletion robustness improvements Import paths using `..//'` were invalid and broken. Replace with explicit module names (`skill-discovery-sources`, `skill-install-filesystem`, etc.) to clarify dependencies. - Bind WSL filesystem methods to preserve `this` context - Keep recovery journal when rollback rename fails, so startup can retry - Skip symlink-based tests on Windows where they cannot run - Only treat ENOENT/ENOTDIR as empty directories; propagate other errors - Fix cross-platform path parent calculation to handle drive roots - Replace shared constant with localized string for user-facing message - Use `runProcess` for WSL integration test instead of bare `execFile` * Add batch limit for skill deletion and improve host availability checkin - Limit concurrent deletions to prevent remote host overload - Add retry logic for capability probing to handle transient unavailability - Add reprobe() method to recheck capability after errors or user refresh - Fix status logic: receipt cleanup is best-effort, completion depends only on content removal - Improve error message for unreachable hosts |
||
|
|
4218d5068e |
fix(cli): seed nvm's default version, not the newest install (#16420)
* fix(cli): seed nvm's default version, not the newest install #16314 stopped the login-shell probe inheriting the seeded PATH, but left the seed itself picking the newest installed nvm version. That ordering decides which node a CLI runs under whenever the probe does not land — a timeout, or a login shell whose rc never initializes nvm — and newest is precisely the wrong guess: it is usually the version the user just added and has installed nothing into. That is the root cause reported in #10932. Resolve `alias/default` instead, mirroring nvm: follow the alias chain (`default` -> `lts/*` -> `lts/krypton` -> a version), resolve a partial version like `24` to the highest matching install, and treat `system`/`node`/`stable` as no preference. The chain is bounded and cycle-guarded because nvm's own resolver tracks seen aliases and hand-edited files can point at each other. Ordering is a preference, not a restriction: the remaining versions stay behind the default, so a CLI installed outside it is still reachable. Measured on a real machine with nvm default=24 and a bare v26.7.0 installed: the old resolver seeds v26.7.0/bin (no CLIs), the new one seeds v24.18.0/bin (every CLI). Tests were written first and verified to fail on the three bug cases against main before the fix existed. Also raise the probe budget from 5s to 10s. The old value was never measured against a real profile: a bash -ilc loading nvm, rvm, conda and gcloud takes ~1s idle but 6-7s on a loaded machine, so a cold start under load silently fell back to the seed. Startup does not block on the probe, and the one awaited consumer is agent detection, which is better served by a probe that finishes late than one that gives up early. * fix(cli): reject non-version alias tokens instead of matching v0.x Review finding, and a real bug I introduced. parseVersionSegment coerces every unparseable segment to 0, so an unresolvable default alias — `garbage`, `iojs`, `lts/nonexistent`, any hand-named alias — became [0] and prefix-matched a `v0.12.x` install, or any stray non-version directory. Orca would then seed a decade-old node as the preferred runtime. Real nvm answers N/A for all of them. The `wanted.length === 0` bail could never have caught this: ''.split('.') is [''], never empty. Replaced with a shape check that still admits legitimate numeric prefixes — verified against nvm itself, which resolves `24` to v24.18.0 and `0` to an installed v0.x while answering N/A for the rest. Also corrects two comments that no longer described the code: the seed is no longer "newest install", and the probe budget note claimed startup never blocks on hydration, which is false on packaged Windows where it gates terminal services and git. The traversal-guard comment claimed a containment join() already normalizes away; the real guarantee is that matchNvmVersion can only return an entry of the versions directory. * fix(cli): match nvm's version-token grammar, not just its first character Round-2 review finding, and the same bug one layer down. The previous guard anchored only the first character, but parseInt stops at the first non-digit, so `0x18`, `00` and `0abc` still parsed to [0] and prefix-matched a v0.12.x install — the decade-old-node seed the earlier fix was supposed to close. Reachable: `nvm alias default 0x18` warns that the version does not exist and writes the alias anyway, then resolves it to N/A. Use nvm's actual grammar, leading zeros included — nvm calls `00` and `024` N/A while parseInt reads them as 0 and 24. Verified by executing 17 tokens against a five-version fixture: every one now agrees with nvm, including the legitimate prefixes `0`, `0.12`, `24` and `v24.18.0`. Also drops a dead disjunct (the hop bound already caps the loop, so seen.size can never exceed it) and corrects the log comment in index.ts, which still told the reader a failed probe leaves the newest install in front. It leaves the default version in front now, which is usually survivable but still not what the shell would have resolved. * test(cli): skip the lts/* chain fixture on Windows Round-3 review finding. makeNvmHome materializes each alias as a real file, and the chain case uses nvm's actual `lts/*` alias — `*` is a reserved Win32 filename character, so writeFileSync fails with EINVAL. PR CI runs a Windows allowlist that excludes this file, so the breakage only reaches a Windows developer running the suite locally. Skipped rather than renamed: `lts/*` is the alias nvm really ships, and the assertion pins platform: 'darwin' anyway, so the real name costs no coverage. Matches the skipIf convention already used across src/shared. Also reflows a comment line that a previous edit ran to 143 characters; oxfmt does not reflow comments, so nothing would have caught it. |
||
|
|
822087c8ec |
refactor(git): split runner.ts into focused command-runner modules (#16395)
* refactor(git): split runner.ts into focused command-runner modules * chore(ratchets): repoint child_process and wsl.exe allowlists at the split modules --------- Co-authored-by: Neil <n@example.com> |
||
|
|
1f39c93b01 | refactor(ipc): split ssh.ts into focused modules (#16394) | ||
|
|
fcf55f2d68 |
fix(terminal): stop Orca mangling the OMP/Pi title it writes itself (#16381)
* fix(terminal): collapse identity group in the title churn signature Replaces the ingest-time title rewrite from #16373 with a non-destructive fix at the actual cause. The churn suppressor `isDecorativeAgentTitleFrameChange` keyed on the literal label, so `working:OMP` and `working:Pi` compared unequal and every alternating frame from a wrapped harness committed a store patch. #16373 made the labels agree by rewriting the stored title to the tab's launch owner — but `runtimePaneTitlesByTabId` is also the Windows Shift+Enter byte-encoding input, so normalizing at ingest destroyed evidence other consumers read (fixed separately in #16376). Collapse the identity group inside the signature instead. Which member of a group a frame names is decoration, exactly like the spinner glyph the signature already strips, so frames compare equal without touching what is stored. Suppression now changes only WHETHER a frame commits, never WHAT it says. Also fixes the flap under a multiplexer (#8032): the collapse runs over wrapper segments, so "zsh | ⠋ Pi" and "zsh | ⠙ OMP" compare equal, which the anchored owner-relabel in #16373 never matched. Reverts the store changes from #16373 and drops the helper it added. Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com> * fix(terminal): fold only bare identity frames into the group token A legacy "π - <session> - <cwd>" title is Pi-compatible too, so folding every profile match collapsed two different sessions to the same signature and suppressed the change outright — reintroducing #16093 through the churn signature. Fold only exact bare identity frames, matched per wrapper segment, so semantic session titles keep comparing on their own text. Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com> * docs(terminal): correct the flap diagnosis in the repro header Verified against the OMP source: it emits only π-glyph frames (`DEFAULT_TERMINAL_TITLE = "π"`, title-generator.ts:25), and on an Orca-hosted pane its native titler cedes to Orca's own injected extension, which writes `⠋ π - <session> - <cwd>`. So OMP emits neither "OMP" nor "Pi". Both flap sides are Orca's: "OMP" from driveSyntheticTitleFromHook, "Pi" from normalizeTerminalTitle collapsing our own extension's output to a hardcoded literal. The prior header credited the wrapped harness for frames it never sends, which is the same wrong narrative that produced eight fixes at eight layers. No behavior change. * fix(terminal): stop Orca mangling the OMP/Pi title it writes itself Verified against the OMP source: it emits only π-branded frames (`DEFAULT_TERMINAL_TITLE = "π"`, title-generator.ts:25), and on an Orca-hosted pane its native titler cedes to Orca's OWN injected extension, which writes `π - <session> - <cwd>` / `⠋ π - <session> - <cwd>` at 80ms. So neither flapping string came from OMP. Orca made both: "Pi" — normalizeTerminalTitle collapsing our extension's output to a hardcoded literal, discarding the session name and cwd (#16093) "OMP" — driveSyntheticTitleFromHook injecting over it every 80ms Fixed at the source: - normalizeTerminalTitle canonicalizes only the rotating braille frame and keeps the rest, in both spinner positions and through a multiplexer prefix (#8032). Status still round-trips through normalization. - detectAgentStatusFromTitle reads the π state separator, so `π ! <label>` is permission instead of the blanket idle that hid a blocked agent. - normalizeCompatibleAgentTitleForOwner swaps only the brand for the owner's label, so a pane still reads as its launch owner (#6689, #7633, #9077) without losing the session text. - pi/omp set synthesizeWorkingTitle: false — the agent animates its own working title. Terminal states still synthesize; they carry the pane's agent identity downstream. Reverts the ingest-time title rewrite from #16373, whose normalization of runtimePaneTitlesByTabId also changed Windows Shift+Enter bytes (#16376). Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com> * fix(terminal): match the state separator only in exact profile casing The separator check runs on every title, so `omp - deploy notes` and `pi - refactor the parser` read as an idle agent. The owner rewrite only ever emits the exact profile labels, so dropping case-insensitivity keeps `OMP - tmp` classifying while ordinary prose stops matching. Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com> * test(terminal): pin one real OMP turn to two committed patches Drives 30 working frames as Orca's injected extension emits them plus the idle transition, and asserts what survives the churn gate. Before the fix every frame alternated "⠋ Pi"/"⠋ OMP" and each one committed — ~12 store patches per second on a working tab. Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com> * fix(terminal): carry the permission guard inside the separator reader `-` is both a π state separator and the delimiter in the synthetic permission label, so `OMP - action required` read as idle. It resolved correctly only because detectAgentStatusFromTitle happens to check the synthetic label first — and the separator fn is exported, so a direct caller inherited the bug. Also pins the owner rewrite's fixed-point property, which holds only because getAgentLabel does not tokenize omp/pi, and corrects a comment that overstated how tightly the brand swap is scoped. Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com> * docs(terminal): name the flag the code actually sets The suite header cited `synthesizeTerminalTitle: false`; the profiles set `synthesizeWorkingTitle: false`. The distinction is the whole reason the narrower flag was chosen — terminal-state frames still carry the pane's agent identity downstream — so the wrong name buried the rationale. Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com> --------- Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com> |
||
|
|
33587fb77d | refactor(git): split status.ts into source-control modules (#16393) | ||
|
|
127fa7fae0 |
refactor(ipc): split repos.ts into focused modules (#16392)
* refactor(ipc): split repos.ts into focused modules
* test: point repo notification mocks at the extracted module
* fix(ipc): repoint the child-process allowlists after the repos split
The type-only `import type { ChildProcess }` moved from repos.ts to
repos/repo-clone-lifecycle.ts, so the import-boundary entry follows it and the
windows-console entry (now stale, and that list only shrinks) is dropped.
Fixture-only; the base file had no runtime child_process use at all.
|
||
|
|
e7c5263a93 |
perf(git): make local Git metadata observation event-driven (no scheduler) (#16404)
* perf(git): make local Git metadata observation event-driven Replaces the recurring per-repo metadata scan with native filesystem events on macOS, Linux, and Windows. Polling is retained purely as a fallback. - Narrow @parcel/watcher stream over <common>/worktrees, extended from macOS to Linux and Windows, with the Windows backend pinned explicitly. - New shallow watcher mode over the allowlisted primary metadata leaves. It watches the containing directory rather than each file, so Git's atomic write-and-rename does not orphan the binding. - Selected upstream refs stay on the existing bounded stat poll. Verified on real hosts rather than in principle: - Windows: `git worktree remove` and `git worktree prune` both succeed while the narrow stream holds the directory. The historical concern that an open handle would block prune does not reproduce. - Linux: inotify costs one instance per event loop, not one per watch, so the watch budget is not a constraint. - macOS/Linux/Windows: shallow events survive repeated commit, checkout, config, and pack-refs cycles. Failure handling, each reproduced before being fixed: - fs.watch binds an inode and reports nothing once that inode is replaced, with no error. Directory bindings are re-checked on a bounded cadence and rebound. - A host whose notification path is dead accepts registrations and stays mute forever. Observed on a macOS machine whose fseventsd had grown to ~15GB and saturated a core. A one-shot delivery probe now fails the shallow subscribe on such a host so it falls back to polling instead of showing stale metadata. This change stands alone on main and does not depend on the metadata poll scheduler. * test(git-watch): hold reserved inodes across root replacements Linux returns a released reservation to the free list, so the second replacement could land back on the first replacement's inode and look unchanged to reconciliation. Verified on ext4: releasing yields inodes [N, N+43, N+43] while holding yields [N, N+43, N+44]. macOS never recycles, which is why this only failed on CI. * refactor(git-watch): share one single-flight helper between watcher fallbacks Both fallbacks tracked their in-flight promise with the same self-comparison on settle, duplicated verbatim. Hoisting it removes a subtle invariant that was being hand-maintained in two places. * fix(git-watch): close the silent-staleness paths in primary metadata Two independent reviews converged on the same root cause: nothing bounded how long primary metadata could stay wrong once the shallow watcher stopped reporting. Four distinct paths led there. - A terminal watch error arriving while the status-ref poll was still starting left the repo with status-ref coverage only. handleWatcherError ran its teardown against nulls, then the in-flight poll installed itself, and the fallback guard mistook it for coverage and discarded the fallback. Primary metadata was then never observed again. The guard no longer treats status-ref polling as primary coverage, and startup re-checks watcher liveness after its awaits. - Nothing re-read the six primary files while the watcher was nominally live. A lossy notification path, a dropped batch, or inotify queue overflow raises no error, so the error-driven fallback never fired and the inode rebind sweep does not detect loss. A 15-tick backstop re-stats them, turning permanent staleness into one tick. Measured cost is ~0.2 stats/s/repo against the 3/s the old poll cost. - Reconciliation treated any late-observed entry create as a root replacement, so an ordinary tore down a healthy stream ~30s later and opened a deaf window. It now also requires the root itself to be recreated. - The shallow watcher recorded directory identity from a stat issued after binding, so a replacement in that gap pinned the dead inode's watcher to the new identity and the sweep would never rebind. Identity is now read first, which errs toward a harmless extra rebind. Test helper: replacing the worktrees root frees several inodes at once, so reserving one still let the recreated root reuse its own. It now verifies the inode actually changed. Confirmed on ext4, where three holds were needed. |
||
|
|
8217e6838f |
refactor runtime contracts and web transports (#16197)
* refactor runtime contracts and transports * test(web): repoint two-phase timeout seam at the transport that now owns call() |
||
|
|
d456549c22 |
refactor runtime Git bridges (#16189)
* refactor runtime git bridges * fix(test): avoid computed namespace import access in runtime Git client contract test |
||
|
|
f482725355 |
refactor terminal coordination modules (#16335)
* Refactor terminal coordination modules * preserve terminal completion and stale-connect guards * restore pre-spawn E2E barrier and stale-connect check order in ipc-pty-connect * restore merge-base title-working replay and stamped-tail delete semantics * fix(terminal): merge the duplicated shortcut-matching import Two adjacent imports of the same module tripped oxlint's no-duplicate-imports under --deny-warnings. Import-only; no behavior change. |
||
|
|
fc0d947983 |
refactor(rate-limits): split Codex and Claude fetchers (#16140)
* refactor(rate-limits): split Codex and Claude fetchers * refactor(rate-limits): restore base error-message defaulting The split moved the 'Unknown error' fallback from inside String() to the call site, which changed behavior for an Error with an empty .message: base surfaced '', head surfaced 'Unknown error'. Restore the base form. |
||
|
|
5ebc4bb2bc |
refactor(renderer): split repos store slice (#16191)
* refactor(renderer): split repos store slice * test(store): align repos/folder-workspace characterization tests with main's owner-scoped delete + host-qualified visit recency * fix(composer-state): repoint RepoUpdate import to repos/repo-state after split |
||
|
|
a782935ab3 |
refactor(checks): split panel responsibilities (#16186)
* refactor(checks): split panel responsibilities * fix(checks): consolidate comment audience import * fix(checks): handle open review state exhaustively * fix(checks): restore merge-base check presentation values The split into check-presentation.tsx silently changed icon, opacity, and PR-state token values. Restore them verbatim. |
||
|
|
83ffc0df24 |
refactor Electron facilities modules (#16333)
* refactor oversized Electron facilities * fix interactive process timeout and shortcut repeat guard * chore(child-process): drop stale cli-installer allowlist entry cli-installer.ts now routes privileged spawns through runProcess via cli-privileged-processes.ts, so the shrink-only ratchet flags it as stale. * refactor(child-process): extract the bounded output sink runProcess's timeoutMs opt-out (required to preserve the unbounded osascript admin prompt) pushed run-process.ts past the 300-line cap. Move createOutputSink to its own module rather than add a max-lines bypass, which AGENTS.md forbids. Moved verbatim; no behavior change. |
||
|
|
1cf562deea | refactor: split source control AI modules (#16179) | ||
|
|
e0bc4e1126 |
refactor(editor): split notebook, editor, and external-watch surfaces (#16143)
* refactor(editor): split editor and watch surfaces * fix(editor): revert behavior changes smuggled into the surface split Restore merge-base React keys in IpynbCellOutputs: the content-identity keys JSON.stringify'd every output value, including raw base64 image payloads, on every keystroke. Collapse the duplicated lazy() declarations into editor-lazy-views so each viewer keeps a single React.lazy identity across the extracted surfaces. |
||
|
|
6103b78e3c |
fix(crash-reporting): correlate concurrent process deaths instead of blaming the renderer (#15251)
* fix(crash-reporting): correlate concurrent process deaths on a renderer report
Two 1.4.184 reports (a326935a, 1862f316) are renderer "crashed"/-1 crash reports
whose renderer only died alongside a sibling Chromium child that died at the same
instant:
F0BQMB30GJX network.mojom.NetworkService crashed/-1 -21ms -> renderer crashed/-1
F0BRPP8TC0Y audio.mojom.AudioService crashed/-1 -2ms -> renderer crashed/-1
GPU crashed/-1 +180ms
process-gone-classification.ts classifies each event in isolation:
isRecoverableChromiumChildProcess discards the utility/GPU halves as recoverable
churn, and `if (reason !== 'killed') return true` then reports the renderer half as
a genuine renderer crash before any cross-source signal exists. Triage reads
"renderer crashed" for what died with three other processes.
process-gone-sibling-correlation keeps a bounded ring of child deaths, populated
before the suppression early-return so churn-suppressed siblings stay visible, and
matches a renderer death against child deaths sharing its failure signature.
What the timing can and cannot support:
- The window is asymmetric. 1s of lookback (a child that died first can plausibly
have taken the renderer with it), but only 250ms of lookahead: a child dying well
after the renderer is at least as likely to be an effect of it — Chromium tearing
down the dead renderer's channels, or renderer_recovery_reload at +264ms — and a
symmetric window would retro-label a genuine lone crash as collateral.
- crashAttribution is 'concurrent-process-deaths', not a causal claim. The largest
1.4.184 cluster is an external taskkill /T where renderer and children are
co-victims; no sibling caused anything there.
- The verdict is not derived from timing alone. A host with a child looping at the
observed 1459/min drops a death into every window, so crashAttribution is set only
when the nearest sibling is within 250ms and no identity repeats. Looser or
repeating deaths still ship as evidence (siblingProcessDeathCount, signed offsets,
siblingProcessDeathRepeats) with no attribution.
- The signature match buckets `crashed` with `abnormal-exit` and gates on the exit
code only on win32. Both fixtures are win32, where every process in a collateral
pair reports crashed/-1; POSIX surfaces a per-process wait status, so an equality
gate would mean this never fires on macOS or Linux.
The report stays reportable and gains evidence rather than being suppressed
(#14667). Both arrival orders are covered without delaying persistence: a sibling
that dies first is folded into the initial record, a sibling that dies after amends
the record already on disk through attachDetails, the same way the minidump
signature does. Late amends are capped at two per report and skipped when the
rendered evidence is unchanged, so a crash-looping child cannot rewrite the store
during renderer recovery, and a failed amend now leaves a
sibling_attribution_attach_failed breadcrumb instead of vanishing.
Relationship to #12484: it is still OPEN and adds process-tree-kill-window.ts, the
same ring/lookback bookkeeping with a 250ms settle, patching the same recorder
hunks with the opposite policy (suppress the killed/1 renderer report instead of
keeping it). #14667 is test-only — it pinned the keep-the-report policy in tests, it
did not remove a shipped implementation. #12484 has to be closed or rebased out
before this lands.
* chore: remove merge hook formatting drift
|
||
|
|
8e8c6d7ed2 |
fix(linear): make new-issue dialog popovers scrollable (#16382)
* fix(linear): make new-issue dialog popovers scrollable `[data-slot='popover-content']` already caps every popover to `--radix-popover-content-available-height`, but PopoverContent's base class is `overflow-hidden`. A team list taller than that cap is therefore clipped at the window edge with no scrollbar and no way to reach the entries past the cut. The dialog's other attribute popovers had an inner max-h-60 box, but none of the six carried the popover-scroll-content / popover-wheel-scroll marker that popover.tsx's wheel shim needs, so Radix's dialog scroll-lock swallowed the wheel there too. Move all six to the popover-scroll-content pattern already used by LinearItemDrawer, JiraIssueWorkspace, and github-item-dialog: it re-declares the cap as min(15rem, available-height) and adds overflow-y: auto, and the class name opts the content into the wheel shim. Measured on the team switcher with 25 teams: before max-height 611px, overflow-y hidden, 609 of 735px visible after max-height 240px, overflow-y auto, scrollTop reaches 497 The inner max-h-60 boxes are dropped because stacking them under the outer cap creates nested scrollers whose combined height exceeds it, leaving the bottom of each list unreachable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(linear): match the inner-scroller classes regardless of order The previous assertion pinned one exact class order, so reintroducing the wrapper as `scrollbar-sleek overflow-y-auto max-h-60` slipped through. Collect the section's `<div>` classNames and check the three tokens as a set instead. Scoped to wrapper divs on purpose: the dialog's description textarea caps its own growth with those same classes and is not a popover child, so a plain whole-section match flags it as a false positive. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
49acb93e2e |
fix(daemon): let PTY inventory recover from a dead terminal host (#16363)
Worktree removal inventories PTYs through DaemonPtyAdapter.listProcesses. That called ensureConnected bare, so once the terminal-host pipe was dead the removal failed with `connect ENOENT \\?\pipe\orca-terminal-host-...` and stayed broken until the whole app was restarted. spawn already wrapped its work in withDaemonRetry and recovered from exactly this. Inventory did not — so the one path that must not get stuck was the only one that could not heal itself. Both the connect and the listSessions request go inside the retry: a host that dies between them throws the same daemon-gone error, so retrying only the connect would still fail. The reconciliation after the request is deliberately outside it; retrying that would be wrong. Reproduced first, with a real daemon killed mid-test: listProcesses threw DaemonConnectionLostError while a control asserting spawn recovery from the identical kill passed. Both are now regression tests, so the asymmetry cannot come back silently. Co-authored-by: innocarpe <innocarpe@users.noreply.github.com> |
||
|
|
aa4c9c707c |
Refactor mobile home, worktree modal, and RPC client (#16165)
* refactor(mobile): split home modal and rpc client * fix(mobile): restore render-phase remount key in NewWorktreeModal The split moved the form-reset epoch from render-phase refs into useState + useLayoutEffect, which changed when the remount key is computed. On the render where visible flips false->true the key was still the old epoch, so the previous session's NewWorktreeModalContent rendered with visible === true carrying stale form state. Child layout effects run before the parent's, so visible-gated hooks (useNewWorkspaceRepositories, useNewWorktreeDrawerNavigation, useNewWorkspaceRuntimeContext) fired for that stale instance before the parent bumped the epoch and remounted. Restore the ref-based computation so the key is correct on the first render where visible flips true, keeping the composite open/client epoch semantics and the file split intact. |
||
|
|
656e4891de |
fix(ssh): restore the reconnect model-paint gate dropped by #15166 (#16361)
* fix(ssh): restore the reconnect model-paint gate dropped by #15166 #15166 split pty-connection.ts and dropped the "paint from main's model on SSH reconnect" half of the reattach gate that shipped in v1.4.188 (#14844), leaving only the park-reveal half. A non-park SSH reconnect has repainted from the ~100KiB relay tail ever since, which cannot rebuild a full-screen frame whose start it no longer holds. Restores followsDirectSshReconnect (PENDING-only retry read), reconnectMayUseModel, the exited-transition veto computed before the probe, and the kitty scanReplay layered after the snapshot baseline. Adds a call-site test over createReattachPayloadHandlers, because the surviving pure-function test stayed green through the entire removal. Fixes STA-5395 * fix(ssh): restore empty-tail reconnect snapshots |