Commit Graph
7581 Commits
Author SHA1 Message Date
OrcaWin 6ddd86ae4e test(ssh): grant source credit in IPC fixture 2026-07-29 15:40:03 -07:00
OrcaWin 728ed98ab7 docs(ssh): bind validation to current main 2026-07-29 15:32:21 -07:00
OrcaWin adba3410fe Merge remote-tracking branch 'origin/main' into ssh-relay-pty-backpressure-2
# Conflicts:
#	config/reliability-gates.jsonc
#	src/main/ssh/ssh-relay-session-reconnect-incarnation.test.ts
#	src/main/ssh/ssh-relay-session.ts
#	src/relay/relay.ts
2026-07-29 15:28:26 -07:00
OrcaWin 40150d9c66 docs(ssh): record always-on relay validation 2026-07-29 15:24:38 -07:00
OrcaWin bdeb9759d8 feat(ssh): make PTY source credit the default 2026-07-29 15:23:35 -07:00
OrcaWinandOrcaWin dde72f85de fix(windows): separate updater from orchestration migration (#11405)
* fix(windows): separate updater from orchestration migration

* fix(terminal): attest adopted reveal identity

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-29 15:23:06 -07:00
Brennan Benson 857f28554b fix(mobile): keep the provider session id alive while an agent sits idle (#11260)
Mobile Chat UI subscribes to an agent transcript by providerSession.id, so
losing that id blanks the chat: use-mobile-native-chat-session clears the
message list and then returns without subscribing when sessionId is null.

Two places dropped the id on a status ping that carried no session metadata,
both while the agent was idle at its prompt — exactly when mobile reads it:

- The renderer store refused to carry the id across `done`. A completed turn
  does not end the provider session (the TUI stays alive and resumable), and
  OSC 9999 repaints plus reconnect snapshot replays re-deliver a metadata-less
  `done` onto an already-done row, so retention has to cover done -> done.
- The main-process OSC ingest overwrote the cached row without the id. The OSC
  wire payload has no providerSession field, so an OSC observation is never
  evidence the session ended. Dropping it there erased the id from persisted
  rows (lost across restart) and from headless `orca serve`, which serves those
  rows to mobile directly rather than through the renderer store.

Both keep the turn boundary: a new turn after `done` still starts clean, so a
reused pane cannot inherit a finished session.

Closes #10630
2026-07-29 12:36:26 -07:00
Brennan Benson 7477a01f3c fix(mobile): stop a mobile reveal from minting a duplicate terminal tab (#11259)
Revealing a live PTY from a paired phone passes the tab id baked into
its env, but the create-terminal handler only recognised ownership via
tab.ptyId and the live ptyIdsByTabId map. With the worktree closed on
the desktop no pane is mounted, so ptyIdsByTabId is empty and tab.ptyId
holds at most one leaf's PTY -- a split pane's second PTY lives only in
the persisted layout. Ownership missed, and createTab ran with an id
that already existed; it mints a fresh uuid on collision, so a second
tab appeared on the same session (#10486).

Resolve ownership through the persisted layout as well. Every binding
consulted is an exact match on the ptyId, so all of them outrank the
reveal's tab id hint: that hint is written once when the PTY spawns and
is never rewritten, so it goes stale as soon as a pane is dragged to
another tab. A mounted pane outranks a recorded one, same-tier
conflicts stay ambiguous so the mobile mount planner keeps failing
closed, and the hint is what resolves a reveal that nothing records
yet -- which is also what keeps paneKey hook attribution intact.
2026-07-29 12:32:54 -07:00
Brennan Benson c74fb3c71b feat(browser): let Shift invert link routing instead of always forcing the system browser (#10991)
* feat(browser): let Shift invert link routing instead of always forcing the system browser

Shift+Cmd/Ctrl-click has always meant "open in the system browser", which is a
no-op when that is already where links go. Users who keep Link Routing off have
had no gesture to pull a single link into Orca's built-in browser.

Adds "Hold Shift to open in ___", a nested toggle under Link Routing that makes
the modifier open a link the opposite way from the setting. It ships off, so the
one-way escape hatch is unchanged for every existing user.

The title and description name the destination the modifier actually reaches and
flip with the parent setting, since "the opposite" is meaningless on its own.

- openHttpLink gains modifierHeld; resolveModifierRouting owns the decision so
  every surface (terminal URLs, OSC 8, xterm web links, markdown preview) routes
  identically. forceSystemBrowser stays for callers that must bypass settings.
- The terminal hover hint names Orca when the modifier would open there, and is
  re-resolved per hover so toggling applies without recreating panes.
- Link Routing's own copy drops "always uses your system browser", which the new
  toggle can falsify; the nested row states the live destination instead.

* fix(browser): route the Checks panel hosted-review link through the shared modifier

The "Open on GitHub" button had its own Shift+Cmd/Ctrl escape hatch that passed
forceSystemBrowser directly, so it kept the old one-way behavior while every
other surface honored the invert setting. Route it through modifierHeld like
the terminal and markdown paths.

Also wraps the modifier row's description in translate(); the title in the same
file was localized but the description returned raw English (caught in review).

* fix(browser): make link-routing modifier copy true in every state

Review follow-ups on the Shift-inverts-routing change.

- The nested row promised "⇧⌘+click opens one in Orca" in the present tense
  while its own toggle was off, so the out-of-box state described behavior the
  user did not have. Phrase it as enabled-state copy, matching sibling rows.
- The parent Link Routing description gained "opens a link the other way",
  which is false in the default state and contradicts the child row when
  inverting is on. No fixed sentence there is true in every state, so the child
  row — which knows the live destination — now owns the claim. That leaves
  getBrowserLinkRoutingShortcutLabel unused, so drop it.
- The rich markdown editor still forced the system browser while the preview of
  the same file honored the modifier, so one link routed two ways depending on
  which view it was clicked in.
- A remote runtime pins every link to the system browser, so the hover hint
  could promise Orca for a click that lands elsewhere. Gate the hint on the same
  condition openHttpLink uses.
- Index both modifier titles: the search entry is built with openLinksInApp
  false, so the row was unfindable by the title it renders when routing is on.
- Drop the ariaLabel that shared no words with the visible label (WCAG 2.5.3)
  and add the four missing settings-search keyword keys to all five catalogs.

* test(editor): pin the rich markdown Shift+click routing hop

The editor half of the modifier fix had no coverage — reverting it to
forceSystemBrowser left the suite green while the same link routed one way in
the markdown preview and the other way in the rich editor. Also pins that a
non-local source owner survives the hop, since that is what keeps an SSH file's
links out of Orca's browser.

* test(editor): cover the Ctrl chord for rich markdown link routing

This file is the only test of handleRichMarkdownEditorClick, and it exercised
metaKey alone, so the isMac branch of modKey had no coverage off macOS. Also
stop claiming the source-owner case proves SSH links stay out of Orca — it
proves the owner survives the hop; http-link-routing.test.ts enforces the rest.

* style: trim review comments to the one-line house rule

Both explained the change adequately in two lines; the extra lines were worked
examples, not information.

* fix(browser): keep Link Routing copy unchanged until inverting is enabled

Removing the "⇧⌘-click always uses your system browser" sentence outright
reworded the row for every user on upgrade, including everyone who never turns
the opt-in on. Restore it verbatim in the default state and only hand the chord
sentence to the nested row once inverting makes "always" untrue.

* revert(editor): keep rich markdown Shift+click on the system browser

Per Brennan: the editor's Shift path hands the link to the client OS and should
not follow the invert setting — the preview opening in Orca is the intended
divergence, not a bug. Restores main's call exactly; the test now pins the
divergence so a future consistency pass cannot erase it silently.

* fix(browser): surface the inverted modifier on the hosted-review link

The hosted-review click path now passes modifierHeld, so with inverting on and
Link Routing off the chord opens in Orca — but the hint stayed gated on
openLinksInApp, hiding a live gesture. Resolve the destination instead of a
boolean. Default-off output is unchanged.

* test(browser): pin the inert modifier hint when links already open in Orca

* refactor(terminal): require the pane link hint so dropping the wiring fails to build

The optional option fell back to a duplicated copy of the legacy hint string, so deleting the hook wiring reverted the tooltip silently with every test green.

* fix(browser): trim the runtime id before hiding the hosted-review modifier hint

openHttpLink and terminalUrlOpenHintOptionsFor both trim, so a blank runtime id hid the hint while the click still reached Orca.
2026-07-29 12:30:48 -07:00
Brennan Benson caca5d1c96 fix(mobile): route external mouse/trackpad wheel through the terminal scroll router (#11247)
The mobile terminal WebView only handled touch. Wheel events fell through to
xterm, which either scrolls its own hidden viewport or — in the alternate
screen — emits cursor keys via onData, and the mobile onData bridge forwards
those to sendMobileTerminalQueryReply, which drops anything that is not a
query-reply grammar. Net effect: an external mouse or trackpad scrolls nothing
inside the terminal, and nothing reaches the PTY.

Attach a wheel handler on the terminal surface that reuses the touch path's
router: alternate-screen and mouse-aware TUIs get bounded cursor keys / wheel
reports through the existing validated terminal-input gate, and the normal
buffer gets the same coalesced scrollback scroll as a swipe.

Refs #6863, #8818
2026-07-29 12:27:18 -07:00
OrcaWinandOrcaWin 363e478909 fix(orchestration): preserve active workers across updates (#11271)
* fix(orchestration): preserve active workers across updates

* test(ssh): model absent legacy adoption

* test(orchestration): align compatibility contracts

* fix(windows): escape updater PowerShell booleans

* fix(windows): restore stock uninstall process check

* fix(orchestration): keep recovery off renderer startup barrier

* fix(orchestration): harden legacy recovery migration

* fix(orchestration): close recovery review gaps

* fix(orchestration): complete legacy worker cutover recovery

* fix(orchestration): preserve legacy workers across updates

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-29 11:31:35 -07:00
NeilandOrca 4543bb6826 fix(activity): bound Activity portal readiness oscillation (React #185) (#11326)
* fix(activity): bound Activity portal readiness oscillation (React #185)

React error #185 is "Maximum update depth exceeded" -- an unbounded render loop,
not setState-after-unmount as the crash reports superficially suggest. Verified in
react-dom@19.2.7's production bundle: getRootForUpdatedFiber throws
Error(formatProdErrorMessage(185)) when 50 < nestedUpdateCount. That frame is the
top of all 14 crash stacks across shipped 1.4.156 / 1.4.158 / 1.4.159.

nestedUpdateCount and rootWithNestedUpdates are module-level globals keyed on the
ROOT, not on a component. One runaway loop saturates the counter and the throw
lands on whichever fiber calls setState next, so SortableTab (terminal.workbench),
the Radix Presence/MenuPortal (page.settings) and the sidebar.worktrees button are
innocent bystanders with misleading component stacks -- four boundaries, one bug.

The driver is Activity's portal readiness pass. When 'ready' is unreachable while
'unavailable' stays reachable, updateReadiness flips loading<->unavailable from
inside useLayoutEffect -- React's sync lane, the lane that increments
nestedUpdateCount -- so it saturates and throws rather than merely being slow. A
prior investigation captured this live on Windows via CDP at 239 renders in 847ms.

Bound the oscillation: after ACTIVITY_PORTAL_READINESS_MAX_FLIPS consecutive
loading<->unavailable transitions the latch parks on 'unavailable' until a real
'ready' arrives. The latch rewrites only the hook's output, never the DOM probe,
so a terminal that churns during a slow attach and then genuinely comes up still
releases it. Because the counter is global per root, this also protects every
innocent bystander in the app -- most of the user-visible value.

Extract reconcileActivityPortalThreads and resolveActivityPortalSwap so the portal
loop is testable against the real collaborators. Note the reconciliation keeps its
worktree+tab comparison deliberately: Terminal mounts one TerminalPane per
(worktree, tab) and routes it by worktree+tab, so staging a same-tab pane would
leave the staged slot empty and its readiness stuck on 'loading' forever. Same-tab
switches swap in place via isolatedPaneKey instead.

Co-authored-by: Orca <help@stably.ai>

* fix(activity): coalesce portal readiness updates

* test(activity): tighten readiness regression coverage

---------

Co-authored-by: Orca <help@stably.ai>
mobile-android-v0.0.36
2026-07-29 02:36:54 -07:00
github-actions[bot] ef55429f3d release: v1.4.162-rc.0 v1.4.162-rc.0 2026-07-29 08:50:00 +00:00
OrcaWin 3240c26bce fix(sidebar): contain nested agent metadata (#11336) 2026-07-29 01:47:52 -07:00
NeilandOrca 3a80fbe162 Revert terminal rendering changes from #10692, #10794, #10871, and #10907 (#11338)
* Revert "fix(terminal): avoid flash while restoring parked terminals (#10871)"

This reverts commit 5a6a9e0b28.

Reverted for terminal rendering regressions (flashing, lost content).
Conflict resolution preserves the forwardRef signature from #10433 and
drops the parked-presentation gating #11016 fed with its effective set.

Co-authored-by: Orca <help@stably.ai>

* Revert "fix(terminal): limit pre-paint WebGL resume to macOS (#10794)" and "fix(terminal): stop switch bold flash and Windows lag (#10692)"

This reverts commits 4681edb520 and
8f5a45401f.

#10794 was itself a partial revert of #10692, so both are reverted
together: the Windows retained-WebGL LRU and the macOS pre-paint
(layout-phase) visibility transition that survived it. Terminal
visibility resume returns to passive disposal and recreation on every
platform, and the WebGL context ceiling returns to a flat 128.

Co-authored-by: Orca <help@stably.ai>

* Revert "fix(terminal): release an abandoned synchronized-output frame on reveal (STA-2694) (#10907)"

This reverts commit 97cb32c1cc.

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-29 01:47:20 -07:00
Wooseong KimandOrcaWin 238d3a1ea1 fix(terminal): verify clipboard writes so TUI "Copied" never lies (#10827)
* fix(terminal): verify clipboard writes so TUI "Copied" never lies

Windows/Electron can return from clipboard.writeText without updating the
OS clipboard, so Claude Code / OpenCode OSC 52 copy and terminal selection
copy looked successful while paste stayed empty (#8977, same root as #5611).

Verify standard clipboard writes by reading back after write, surface OSC 52
host write failures with a toast, and route selection copy through a shared
helper that only clears the selection after a confirmed write.

* fix(terminal): harden clipboard write verification

* fix(terminal): contain clipboard failure notifications

* fix(terminal): isolate verified clipboard writes

* fix(terminal): address greptile clipboard verify nits

Drop the dead onWriteFailure pass-through from the coalesced OSC 52
handler so failure toasts stay owned by the microtask path. Cover
multi-line / CRLF identity in write+verify tests, and export the
verification-failed error constant for stable matching.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-29 01:18:31 -07:00
NeilandOrca fa449bc0ef fix(worktree-palette): stop blanked display names from crashing Cmd+J (#11323)
* fix(worktree-palette): stop blanked display names from crashing Cmd+J

Blanking the "Display Name" field made buildWorktreeMetaUpdates emit
`displayName: undefined` as a present key. The store's `{ ...worktree,
...updates }` spread then erased the live name, so the next palette
keystroke threw "Cannot read properties of undefined (reading
'toLowerCase')" in searchWorktrees (crash a1f81ea1, build 1.4.159).

Fixed at three layers so no single guard is load-bearing:
- Producer: persist the blanking intent as '' instead of undefined, and
  let WorktreeSet accept '' so remote/SSH hosts stop dropping the clear.
- Store: applyWorktreeUpdates and applyDetectedWorktreeUpdates drop
  present-but-undefined keys for fields Worktree declares required.
- Readers: resolveWorktreeDisplayName/resolveWorktreeBranchLabel mirror
  the main-side mergeWorktree fallback (custom -> branch -> folder) for
  all four Cmd+J searches, the checks/review index, and the render site.

Co-authored-by: Orca <help@stably.ai>

* test(worktree): assert omitted display name shape

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-29 00:56:26 -07:00
余辉andOrcaWin 4517088c42 fix(gitlab): refresh self-hosted provider detection (#9909)
* fix(gitlab): refresh self-hosted provider detection

* fix(gitlab): preserve auth refresh during host probe

* fix(gitlab): merge refreshed auth hosts linearly

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-29 00:56:05 -07:00
JinjingandOrca a7c8b8e071 fix(terminal): bound SSH & remote hidden-worktree terminal retention (C1) (#10625)
* fix(terminal): park SSH worktrees like local ones (C1 retention, slice A)

SSH ptys were blanket-excluded from hidden-view parking, so a hidden SSH
worktree retained every pane forever (C1: renderer heap climbs to the V8
ceiling). SSH bytes transit local main — fact-mode watchers already cover
them, and main keeps a headless model served over pty:getMainBufferSnapshot
that the SSH reattach path never consulted.

- isParkRestorableTerminalPty: snapshot-backed OR (SSH + policy); threaded
  through both park verdicts, both selectors, watcher coverage, and the
  watcher start guard. Remote-runtime/fail-open/foreign/null unchanged.
- Parked-SSH reveal paints from main's headless model (dimension-matched,
  ~5k rows) and degrades to the relay 100KiB replay unless the snapshot is a
  non-empty source==='headless' payload — never a blank/stale paint.
- Kill switch: settings.terminalSshViewParking (default on).

DESIGN.md records the approved plan and the H1 magnitude non-claim.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): bound hidden-worktree retention with a force-park budget (C1, slice B)

Un-parkable worktrees (remote-runtime ptys, uncoverable tabs, SSH with the
slice-A switch off) had unlimited retention: the parking cap/TTL only ever
saw eligibility-passing worktrees, so one bad tab pinned a whole worktree's
panes forever. Retention is now memory-bounded, not eligibility-bounded.

- terminal-hidden-worktree-retention.ts: retention budget (12 hidden / 45min
  TTL, sized from the measured 2.5-19MB per-pane V8 cost, DESIGN.md §2) over
  hidden worktrees ordinary parking can never evict; reuses the hot-retain
  ranking so last-active exemption, deterministic ties, and deadline-driven
  rechecks hold. Fail-open/foreign-pty tabs are eviction-exempt (a remount
  would fresh-spawn and orphan the live shell).
- Terminal.tsx: force-parked ids join the parked set AFTER the coverage veto
  (darkness for uncoverable tabs is the accepted cost); buffers captured via
  the sleep-flow registry before the unmount render; retention TTL added to
  the recheck deadlines for budget candidates only.
- Verdict stays out of its own effect deps; policy test asserts idempotence
  and time-monotone membership (flip-loop dwell regression).
- Kill switch: settings.terminalHiddenWorktreeRetentionBudget (default on).

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): demote hidden scrollback for eviction-exempt worktrees (C1, slice C)

The retention budget (slice B) must exempt worktrees holding fail-open or
foreign-worktree ptys — a remount would fresh-spawn and orphan the live
shell — which would leave that class unbounded again. Instead, past the same
45min retention TTL their hidden panes drop to the minimum scrollback tier
(measured: ~19MB -> ~1.3MB V8 heap per 50k-row pane; trimmed history is
gone by design, reveal restores the configured cap for future output).

- terminal-hidden-scrollback-demotion.ts: module-state verdict registry
  (parked-watcher pattern) with content-equality notify damping; applied in
  the existing scrollback-rows effect in use-terminal-pane-lifecycle.
- selectScrollbackDemotedTerminalWorktrees: pure, TTL-gated, time-monotone.
- Retention TTL wakeups now also cover exempt worktrees so demotion fires.
- Kill switch: settings.terminalHiddenScrollbackDemotion (default on).

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): paint the SSH model snapshot inline, not via nested coordinator (C1 slice A fix)

applyMainBufferSnapshot runs its own structuralReplayCoordinator.run; calling
it from applyReattachPayload (already inside the coordinator when a relay
replay exists) deadlocks on the coordinator's tail chain. The model paint now
mirrors the daemon-snapshot branch inline (folded scrollback + rehydrate +
screen, dimension-matched, escape tail last) and arms the restored-snapshot
seq baseline so deferred/live chunks the snapshot covers dedupe instead of
double-painting. Also falls through (no early return) so reattachPayloadApplied
still latches. Adds the folder-workspace id parity unit case.

Co-authored-by: Orca <help@stably.ai>

* test(terminal): SSH park+reveal e2e round-trip + as-built design notes (C1)

Docker-gated (ORCA_E2E_SSH_DOCKER=1) spec: SSH tab parks behind a decoy and
reveal restores marker content at multi-viewport scrollback depth. DESIGN.md
records the as-built deltas (inline paint, force-park shape, last-active
floor) and the residuals so follow-ups aren't lost.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): paint SSH reveal from main's model even when the relay replay is empty (C1 review #1)

A relay restart empties the replay buffer; the reveal previously painted
nothing even when main's headless model held the session. The reattach now
prefetches the model snapshot when no structural replay exists (SSH-shaped
ptys only) and paints it inside the coordinator; emptiness is judged on the
composed payload (scrollbackAnsi + data + pendingEscapeTailAnsi) so an
alt-screen snapshot with an empty screen frame still paints.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): decouple scrollback demotion (slice C) from the retention-budget switch (C1 review #2)

Per the approved contract each slice reverts behind its own switch: slice C
now requires only the master terminalHiddenViewParking plus its own
terminalHiddenScrollbackDemotion flag. The TTL wakeup timer fires for
demotion candidates even with the budget switch off. No DEFAULT_SETTINGS
entries exist for sibling flags (defaults are the '!== false' optional
pattern), so no explicit defaults are added.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): scope eviction exemption to the tab, not the worktree (C1 review #3)

One eviction-exempt tab (fail-open/foreign pty) previously vetoed force-park
for its whole worktree, pinning co-located remote-runtime tabs forever. The
worktree now force-parks while exempt tabs keep their mounted panes via a
per-tab exclusion mirroring the Activity-portal pattern (legacy watcher sync,
legacy render, and the overlay cold-parking hook). Ordinary parking is
untouched — a worktree with an exempt tab still cannot ordinary-park.
Slice C now also demotes exempt tabs' panes as soon as their worktree
force-parks under the count budget (they are the only panes left mounted).

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): demote un-parkable worktrees the force-park lever spared (C1 review #4)

The last-active exemption means a single hidden un-parkable worktree never
force-parks — and slice C previously only targeted exempt-tab worktrees, so
its panes held full scrollback forever. Demotion now also covers un-parkable
non-exempt worktrees past the retention TTL that are absent from the
force-parked set (last-active spared, or slice B switched off). Membership
stays time-monotone for fixed inputs; covered by new idempotence/monotone
selector tests.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): keep the hidden clock running through transient background-measure windows (C1 review #5)

Whole-worktree background mounts (browser-automation bootstrap lease, mobile
mounts, agent wakes) open a ~3s self-clearing measure window that previously
deleted hiddenSince — every remount restarted the 30s hysteresis and the
45min retention TTL, so a periodically re-mounted force-parked worktree
never re-parked. The measure window still pauses parking/eviction verdicts
(all selectors skip measuring candidates); only the clock survives, so the
prior verdict resumes as soon as the window closes. Visible and
portal-holding worktrees still reset the clock.

Co-authored-by: Orca <help@stably.ai>

* test(terminal): make the SSH park+reveal depth assertion prove the model paint (C1 review #6a)

Pad the session with ~180KB of output after the numbered markers so the
earliest marker falls outside the relay's 100KiB rolling replay buffer while
staying inside main's ~5k-row headless model; asserting marker_1 after
reveal now proves the headless-model paint rather than passing under the
relay fallback.

Co-authored-by: Orca <help@stably.ai>

* docs(terminal): rewrite DESIGN.md as the single as-built C1 contract (review #7)

One contract matching the code: status IMPLEMENTED around force-park (not
the unmount proposal), real kill-switch names with coupling + revert
matrices, the true retention-floor formula with measured per-pane and
demotion numbers, an explicit when-OOM-is-still-possible paragraph naming
the H2 pendingSideEffects residual, the applyMainBufferSnapshot deadlock
constraint inside the slice-A section, stable-signal phrasing instead of a
capability latch, fail-open AND foreign-worktree exemption class, verified
cites, and a planned/landed/follow-up test matrix.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): resolve the eviction exemption per pane, not per tab (C1 review #8)

isEvictionExemptTerminalTab read only tab.ptyId — the FIRST leaf's pty —
while the coverage veto that makes a worktree a retention candidate walks
every pane. A split tab whose second leaf held an unrestorable pty therefore
failed coverage (→ force-park target) yet looked exempt-free, so force-park
unmounted it and orphaned the live shell. The exemption now resolves panes
through the same resolveParkedTerminalPaneCandidates, keeping tab.ptyId in
the union for the no-layout/no-capture case.

Also from the same review round:
- force-park's capture passes includeLocalBuffers:false like every other
  shutdownBufferCaptures caller; it was serializing up to 512KB/pane of
  scrollback into the store inside a fix meant to bound renderer heap.
- Terminal.tsx unmount resets the scrollback-demotion registry — module
  state with no reset path, read by a pane effect that runs before the host
  effect that would clear it, so a stale verdict trimmed restore replays.
- memoize watcher coverage per tab within the parking pass; the retention
  candidates re-asked it for every mounted worktree, not just the parked few.

* docs(terminal): drop DESIGN.md — the as-built C1 contract moves to the PR body

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): cap the deferred PTY side-effect queue (C1 residual H2)

pendingSideEffects grew without bound under background timer throttling
(~64 drained/s vs hundreds queued/s overnight). Cap at 512 entries with
oldest-first eviction: titles drop (last-wins), a pending bell latches
onto the next survivor, agent-status payloads collapse onto the survivor
keeping the newest 16 (last-wins store state, KB-scale strings).

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): carry command-lifecycle facts through parked watchers (C1 follow-up)

Parked fact-mode watchers omitted onCommandFinished/onCommandCode*, so
OSC 133;D and Command Code scrape signals went dark while parked. New
parked-terminal-command-status.ts ports the store-level subset: git-UI
nudge on every command finish, same-turn status-row drop for SSH PTYs
(exact mounted-path parity — the foreground tracker refuses SSH ids),
and the Command Code working seed / 1500ms done settle. Byte mode scans
the same shared parsers for authority-off parity. Local-PTY status drops
stay with the mounted pane: they need pty-connection's process-confirm
ladder to tell a leaked nested-shell 133;D from a real agent exit.

Co-authored-by: Orca <help@stably.ai>

* test(terminal): retention-budget force-park e2e with a retentionLimit override (C1 6b)

ORCA_E2E_TERMINAL_RETENTION_LIMIT flows preload → e2e-config →
getTerminalParkingPolicyOverrides (exposeStore-gated, positive-integer
only) so a spec can shrink the force-park budget to 1. The Docker-gated
spec opens two remote worktrees on one relay target (second pre-seeded
remote repo), disables terminalSshViewParking to make both un-parkable,
hides both behind the local context, and proves the older one force-parks
while the last-active exemption spares the newest; re-activating the
evicted worktree restores the marker tail via relay replay.

Co-authored-by: Orca <help@stably.ai>

* test(terminal): retention-budget e2e via same-repo remote worktrees (passes docker lane)

The first draft added a second remote repo mid-session, whose pane pty
spawn misroutes to the local daemon with the remote cwd (pre-existing
multi-repo issue, reproducible without any retention override — a seeded
local repo plus one remote repo shows the same misroute). The spec now
budgets across three worktrees of the ONE connected repo, created through
the product createWorktree path (an external git-worktree-add only lands
as a detected worktree needing adoption) and polled through the relay's
transient post-connect reconnect window. Verified green on the local
Docker lane in 20.8s.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): prevent remount thrashing during post-measure cool-down (

Implements the C1 retention contract: preserve worktree `hiddenSinceMs` through a
background-measure window (so TTL/ranking stay honest), but re-park waits for a
full `coldParkDelayMs` cool-down after the measure ends. Without the cool-down,
every ~3s measure lease on a past-deadline worktree thrashes remount/reattach.

Core changes:
- Terminal.tsx: add measure clock (measuringTerminalWorktreeIdsRef) and post-measure
  cool-down tracking (terminalWorktreeParkCooldownUntilRef); gate parking candidates
  until cool-down expires.
- Extract snapshot replay choreography to shared terminal-snapshot-replay-paint.ts
  (used by SSH reattach + daemon restore paths).
- Add SSH model snapshot timeout (750ms) with fallback to relay replay.
- Move cold-park recheck deadline logic to terminal-cold-park-recheck-deadlines.ts;
  add cool-down deadline to scheduling.
- useTerminalTabColdParking: implement matching measure-clock contract with per-tab
  cool-down gate to keep tab deadlines synced with worktree retention clock.
- Add resolveTerminalMountScrollbackRows() to demote new xterms under demoted
  worktrees (pane births during demotion must take the demoted tier at create).
- Add kill switches: terminalSshViewParking, terminalHiddenWorktreeRetentionBudget,
  terminalHiddenScrollbackDemotion.

* fix(terminal): detect Command Code completion in parked mid-turn panes

Seed the byte watcher with in-flight turn state from agent status: the
watcher is recreated per park cycle with no startup command to arm it,
and the banner scrolled away before parking. Also memoize
eviction-exempt checks and use SSH PTY ID builder in tests.

* fix(terminal): flush pending command-code settles on reveal remount

When a parked pane reveals mid-Command Code turn, the new detector
cannot re-observe the already-passed idle composer. Cancelling the settle
leaves the row stranded at 'working', so dispose now flushes the pending
settle instead.

Extract readInFlightCommandCodeTurn to shared space and seed detectors
with in-flight turns so remounts complete mid-flight commands. Also
memoize SSH model probes to prevent double timeouts on reattach.

* fix(terminal): remove scrollback demotion (C1 slice C)

The scrollback demotion feature for eviction-exempt hidden worktrees is no longer needed. Retention budget limits are now sufficient without this additional bound. Remove the terminal-hidden-scrollback-demotion module, the selectScrollbackDemotedTerminalWorktrees function, and related per-pane demotion logic.

* test(terminal): assert bounded probe during stalled reveal

Add assertion to verify that a stalled reveal operation makes exactly one
`getMainBufferSnapshot` call, ensuring retry logic doesn't introduce
redundant probes that would extend the timeout window before relay fallback.

* fix(terminal): implement C1 retention budget for hidden parked worktrees

Addresses OOM regressions in hidden parked terminals by force-evicting
worktrees past a retention budget: at most 12 mounted while hidden, none
past 45 minutes (absolute, not exempted by last-active). Eviction is
least-recently-hidden-first. Exempt tabs (unrestorable local PTYs) keep
their panes to avoid orphaning shells; worktrees are force-parked even
if they contain exempts, and their buffers released elsewhere. SSH/remote
worktrees serialize buffers pre-eviction for reveal; local worktrees keep
daemon snapshots. Command Code's done-settle window is transferred across
park/reveal boundaries so the row cannot strand at 'working'. Model probe
on SSH reattach is scoped to park-reveal only, not ordinary reconnects.
Includes new E2E suite proving the budget actually releases memory.

* memoize eviction-exempt terminal tabs to avoid redundant store reads

Each tab's exemption check re-reads the store and walks the layout tree.
Introduce selectEvictionExemptTerminalTabIds() to resolve all exempt tabs
for a worktree in a single pass, then memoize the result in Terminal.tsx
and useTerminalTabColdParking. This prevents O(n) store reads when checking
exemptions across multiple tabs and ensures the set remains stable across
unrelated re-renders.

* refactor: reformat hidden-worktree retention comments

Reflow to 80-character lines and remove internal ticket references
(C1, C1 slice C).

* fix(lint): split overlay slot and eviction-exempt tabs under max-lines

Static analysis failed because TerminalPaneOverlayLayer (401) and
terminal-parked-tab-watchers (304) exceeded oxlint max-lines. Extract the
slot component and eviction-exempt helpers into dedicated modules.

* test(terminal): stabilize retention budget e2e control arm

Stage un-parkable remote pty ids only after both worktrees are hidden, and
keep re-staging during the control-arm poll so a late updateTabPtyId cannot
flip the decoy back to park-restorable and ordinary-park it before budget
engages.

* test(terminal): pin retention e2e decoy to a mounted pane snapshot

Use the active pane-identity snapshot for the decoy tab instead of all
worktree tabs, and re-assert un-parkable ids after the control-arm hold so
a deferred/empty tab id cannot fail the budget-off mounted-count check.

* fix: memoize terminal eviction exemptions on layout leaf PTYs

Splits add leaf panes to the layout store without changing the tabs
array. A memo keyed only on tabs misses this change, leaving new panes
unexempted for unmount. Include layout leaf PTYs in the exemption memo
key so it recalculates when splits occur or PTYs are re-minted.

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-29 00:47:18 -07:00
OrcaWin e549dd3ef4 fix(agents): make pane retention transfer-aware instead of suppressor-based (#11310) 2026-07-29 00:35:06 -07:00
Neil b4b3bcdb84 perf(vault): look up resume worktrees through the shared index (#11317)
Resolving a session's resume target scanned every worktree in every repo:
two `Object.values(worktreesByRepo).flat().find(...)` calls, which also
allocate a fresh 1124-element array each time, plus an equivalent `some()`
walk. All three run per visible session row, so a panel render repeated
them ~20 times.

`getIndexedWorktreeMap` already exists for this and is WeakMap-cached on
`worktreesByRepo`, so the index is built once per store snapshot rather
than per call. `connection-owner-resolution.ts` already resolves worktrees
this way.

~1.6ms -> ~0.002ms per render pass at 1124 worktrees. Net -4 lines.

Behavior is unchanged: the map dedupes by id, which only diverges from
`find()` when one id appears twice with different objects. Worktree ids are
`repoId::path`, so a duplicate id within a repo array — the documented race
the index was built for — refers to the same worktree.
2026-07-29 00:20:47 -07:00
Neil 6cc579a48c perf(vault): dedupe scope paths by key instead of rescanning (#11314)
* perf(vault): dedupe scope paths by key instead of rescanning

#11303 took the session maps off the workspace-switch path, but scope path
derivation still follows the active worktree and stayed quadratic.

addAiVaultWorkspaceScopePath deduped by re-normalizing every already
accepted path on each insert, so accepting K paths cost O(K^2)
normalize('NFC') calls — ~632k at 1124 workspaces. isAiVaultWorkspaceScopePathClaimed
separately rescanned every live worktree per prior id, and runs twice per
switch via activeWorktreePaths and scopePaths.

- carry a Set of comparison keys alongside the paths, so each insert is one
  normalize plus one Set lookup
- thread that accumulator from the workspace pass into the project pass
  rather than restarting deduplication against a plain array
- build one comparison-path -> worktree id map for the claim check, keeping
  first-writer-wins to match the previous some() short-circuit

deriveAiVaultScopeSessionPaths on a real 1124-workspace profile:
189.7ms -> 0.8ms. Output is unchanged, including ordering: verified against
the previous implementation across 117 scenario/option combinations
covering monorepo and mixed-repo layouts, priors both claimed and
unclaimed, duplicate paths, NFD/NFC, WSL UNC, trailing and doubled
separators, relative and blank paths, and four project-key shapes.

Adds the first test file for this module: scope semantics (priors, claimed
priors, cross-repo rejection, dedupe, NFD/NFC) plus a timing guard.
Verified fail-first — the guard reports 220ms on the previous
implementation. Path length is chosen deliberately, since normalize() cost
scales with it and short synthetic paths understate the old shape.

* fix(vault): make the claim check independent of worktree ordering

Review catch on the first pass: keying claims by comparison path meant a
duplicate path had to pick one owner, and picking the active worktree
masked a real claimant later in the list. Concretely, with the active
worktree also listed at its own prior path, the prior was reported
unclaimed where the previous some() reported it claimed.

Excludes the active worktree while building the set instead of comparing
ids at read time, so any surviving entry is a claim by construction and
ordering cannot decide the result.

Adds a test over four orderings, verified fail-first against the previous
commit. Also adds a timing guard for deriveAiVaultWorkspaceScopePaths,
which the session-scope guard did not cover.

Equivalence rerun against the pre-optimization implementation: 156
scenario/option combinations, identical paths and ordering.
2026-07-29 00:11:04 -07:00
OrcaWin f3b6979f6a fix(ssh): latch source credit environment override 2026-07-29 00:01:20 -07:00
OrcaWin a3ef00323a fix(ssh): close source recovery review gaps 2026-07-28 23:57:43 -07:00
OrcaWin d07931c4c2 fix(mobile): keep host action drawer close stable (#11306) 2026-07-28 23:50:53 -07:00
Neil c5102e1262 test(vault): guard the workspace-switch regression #11303 fixed (#11311)
#11303 removed the O(sessions x roots) path normalization from the session
worktree map, but nothing fails if that shape comes back. Adds the two
checks that were missing, plus the tool that would have caught it.

- timing guard: 1200 worktrees x 400 sessions must build in <150ms.
  Verified fail-first — restoring the pre-#11303 per-session
  buildWorktreeCandidates call takes it to 238ms; it is ~15ms as merged.
- path boundary: '/repo/alpha-sibling' must not be attributed to
  '/repo/alpha'. Hoisting the root normalization out of the loop must not
  degrade containment into a bare startsWith.

Also adds tools/benchmarks/workspace-switch-paint-latency.mjs, which
attaches over CDP and measures first-paint-after-click and max frame gap.
The existing worktree-switch-responsiveness.spec.ts only times the
synchronous click task, which stays ~1ms because the highlight is a direct
DOM mutation — that is why a ~1s stall could ship without tripping a
budget. On the affected build it read maxFrameGap p50=973ms.
2026-07-28 23:45:05 -07:00
OrcaWin aca05800d9 feat(ssh): add per-target bounded output setting 2026-07-28 23:41:59 -07:00
Brennan Benson 9cf31bdc00 perf(vault): stop rebuilding session maps on every worktree switch (#11303)
Switching worktrees rebuilt two ~500-entry maps in the Agent Session
History panel because their memo deps included the active repo/worktree,
which the maps never read; the worktree map also rebuilt ~530 path
candidates (and re-normalized every root) per session, ~255k
isPathInsideOrEqual calls per switch.

- Drop activeRepo/activeWorktree from the sessionProjectById memo via
  buildAiVaultSessionProjectById, and activeWorktreeId from
  useAiVaultSessionWorktreeMap; 'current' is now stamped per row at read
  time (withAiVaultCurrentWorktreeStatus), so switches reuse both maps.
- Hoist candidate building out of the per-session loop and precompute a
  normalized-root matcher per candidate, so map rebuilds on data changes
  are O(sessions + roots) normalizations instead of O(sessions x roots).

NFC folding from #10841 is untouched; non-ASCII (NFD/CJK) matching is
covered by new tests. Warm switch with the panel on All/500 drops
425ms -> 125ms on the full-scale rig (cold 491ms -> 193ms); panel-closed
switches are unchanged.
2026-07-28 23:08:55 -07:00
Brennan Benson 1df8aa5605 fix(dashboard): give the agent preview terminal a real pane's keyboard (#11015)
* fix(dashboard): give the agent preview terminal a real pane's keyboard

The dashboard's preview terminal is a bare xterm, not a pane, so it never
ran `resolveTerminalShortcutAction` — its only custom key handler covered
copy/paste and IME. Ctrl+Backspace therefore fell through to xterm's default
`\x08`, which readline binds to backward-delete-char: one character instead
of a word.

Route the preview's keys through the pane's own shortcut policy, so word and
line kills, Option chords, modified Enter, and scrollback chords encode
identically. Pane-scoped verdicts (splits, search, focus) are swallowed rather
than passed to xterm, which would send e.g. Ctrl+Shift+D as a bare Ctrl+D.

The policy needs three things the preview could not see:

- kitty-protocol flags — mirrored locally from the same PTY output stream
- the PTY's execution host — bytes follow the host, not the client OS, so a
  new `DashboardCard.terminalInput` profile is derived in the main renderer
  (the only one holding the store) and relayed to the pop-out
- host terminal options — the ConPTY backend and the kitty withhold now apply

Also brings the emulator itself up to a pane's: Orca's Unicode 11 width shim
(replayed CJK/emoji/ZWJ laid out wrong without it), Windows Ctrl+Alt chord
repair, user font/cursor/line-height/word-separator/sensitivity settings,
ligatures, the TUI wheel multiplier, lazy Arabic shaping, clickable links, and
the IME candidate anchor — extracted from pane-lifecycle so both surfaces share
one implementation.

The in-window drawer built its snapshot from a slice subset, which would have
degraded the new profile to client-OS defaults there; it now reads the full
store non-reactively.

* fix(dashboard): enumerate pane-scoped chords instead of a default case

The switch-exhaustiveness gate rejects a `default` over the shortcut-action
union — it would let a newly added action be swallowed silently instead of
forcing a decision at the preview's boundary.

* fix(dashboard): preserve native shortcuts and PTY host routing

* chore: keep merge scope limited to dashboard

* perf(dashboard): avoid full-store copies for terminal profiles

* fix(dashboard): validate terminal input profiles at IPC boundary

* fix(dashboard): sync preview terminal refs on commit, not during render

react-compiler rejects ref writes in the render pass; every reader is an event handler or a post-await continuation, so a commit-phase sync is equivalent.

* test(dashboard): cover the three seams that relay the host-input profile

Reverting any of them left every suite green: the dialog's terminalInput prop
(the only reader of DashboardCard.terminalInput), the drawer's hand-threaded
store slices, and the pop-out's republish triggers. Each new assertion was
mutation-checked against its source line.

* fix(dashboard): follow the WSL host for a preview terminal's byte routing

The card resolver handed resolveTerminalInputHostPlatform a transport with no
getLocalSessionMetadata, so a WSL pty on a Windows client resolved to win32
while its own pane resolves linux — Shift+Enter would then encode CSI-u where
the pane sends alt-enter. Mirror the pane transport's own gate.

* fix(dashboard): republish on every slice the host-input profile resolves from

The compare set covered 4 of the ~11 slices that decide a card's execution
host, so a change to the rest (folder workspaces, project groups, the runtime
catalog, detected worktrees) never triggered a publish. On a quiet board there
is no later publish to heal from, and the pop-out — which cannot re-derive the
profile — keeps encoding bytes for the host the pty used to run on.

* fix(dashboard): sync preview terminal refs on layout, not on a passive effect

xterm's keydown is a native listener, so React never flushes a passive effect
before it. A just-relayed host profile could therefore miss the next keystroke.

* test(dashboard): pin the preview's replay-vs-live kitty scan

The existing A/B passed either way: a lone CSI > u sets the same flags through
scan and scanReplay. Redeliver the push across a snapshot and its replay so
stack semantics would leave the TUI's single pop on a stale frame.

* fix(dashboard): rebuild the drawer's snapshot on every host-input slice

The in-window drawer read ~12 host slices through getState() while subscribing
to none of them, on the premise that agent activity drives the next rebuild. A
quiet board has no such rebuild: an SSH handshake completing with every agent
idle leaves the preview terminal encoding bytes for the pre-connect host, and
agentStatusEpoch only ticks on live status changes so it never heals. Watch the
same set useDashboardPopoutBridge republishes on — each writer bails out when
nothing changed, so the added deps are far quieter than agentStatusByPaneKey,
which already rebuilds this memo on every status ping.

The existing coverage mounted a second hook, which always recomputes; the new
test re-renders the same hook after changing only sshConnectionStates.

* fix(dashboard): key the preview by the user's terminal shortcut policy

The preview passed 11 of the 12 inputs the pane's policy takes and let the
12th default to orca-first. Under terminal-first a remapped tab.close chord is
meant to yield to the shell — Ctrl+W is a word-kill there — but the preview
kept claiming it as a pane close and swallowed the bytes.

* test(dashboard): pin the host-input profile to card snapshots only

The count path main added in #11042 renders no cards, so it must not pay a
per-pty host resolution on every agent-status tick. Both assertions run against
a card that does have a live pty, so only the gate keeps the profile off.

* refactor(dashboard): extract the board's client-host read

The merge of main's label bounding pushed build-dashboard-snapshot.ts to
302 lines. The client's own platform facts are a distinct concept from the
pty host each card keys against, so they move out rather than earn a
max-lines bypass.
2026-07-28 21:57:41 -07:00
OrcaWin f4bad7ab3a test(ssh): align deploy fixtures with credential publication 2026-07-28 21:46:34 -07:00
NeilandOrca aeeae53b1f fix(build): stop pnpm -r from crawling the mobile workspace (#11291)
Co-authored-by: Orca <help@stably.ai>
2026-07-28 21:34:55 -07:00
OrcaWin 5f7330bbbc docs(ssh): record final exact-head validation 2026-07-28 21:31:00 -07:00
OrcaWin 7327640d3a fix(ssh): contain migration failure per PTY 2026-07-28 21:24:47 -07:00
OrcaWin 66c3f4347c fix(ssh): fence model admission across reconnect 2026-07-28 21:04:25 -07:00
Wooseong KimandOrcaWin adc10cd21a fix(explorer): refresh tree on create/rename with case-tolerant cache keys (#10392)
* fix(explorer): refresh tree on create/rename with case-tolerant cache keys

Windows watchers can emit paths whose casing differs from the worktree
dirCache key, so create events never refreshed. Also apply rename events
immediately by refreshing the parent listing instead of ignoring them.

* fix(explorer): reconcile Windows update-only creates

* fix(explorer): bound watcher update reconciliation

* fix(explorer): index watcher cache paths

* fix(explorer): avoid expanded directory rescan

* fix(explorer): batch watcher subtree purges

* fix(explorer): preserve Windows drive roots

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-28 20:52:09 -07:00
afbd98d8a4 Support Windows drives in the remote host filesystem picker (#7439)
* Support Windows drives in the remote host filesystem picker

The remote picker was locked to the system drive on Windows hosts: the
breadcrumb root resolved to C:\ and typed drive paths (M:\dev) were
treated as filter text, so projects could only ever be created on C:.

- Server: answer host-root browses ('/') on win32 with the mounted
  drives instead of resolving to C:\.
- Client: recognize drive-anchored input (M:\, M:/, m:) as path mode,
  resolve segments from the normalized drive root, and make
  joinPath/parentPath/breadcrumbs drive-aware. Up from a drive root
  returns to the host root (the drive list).

Fixes #7438

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Document why joinDrivePath uses a literal backslash

Review feedback suggested path.win32.join, but the renderer bundle
imports no Node builtins anywhere and runs sandboxed, so path.win32 is
not available here. The backslash targets the remote Windows host
regardless of client OS; say so at the call site.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Complete Windows drive browsing over SSH

* fix remote Windows drive browsing

* fix(ui): key remote breadcrumbs by path

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-28 20:51:14 -07:00
余辉andOrcaWin 3f53287554 fix(mobile): accept WebSocket pairing addresses (#9912)
* fix(mobile): accept websocket pairing addresses

* fix(mobile): align manual pairing address validation

* docs(mobile): correct custom address grammar comment

* fix(mobile): enforce pairing endpoint size limit

* fix(mobile): reject canonical IPv6 wildcard addresses

* fix(mobile): handle unscannable pairing offers

* fix(mobile): reset custom address dialog on close

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-28 20:34:26 -07:00
OrcaWin 37c81ff42d fix(ssh): reject reclaimed snapshot replacements 2026-07-28 20:32:34 -07:00
JinHyeok JeongandOrcaWin 6107789c97 Fix WebSocket fallback for reserved Windows ports (#7185)
* Fix WebSocket fallback for reserved ports

* fix(runtime): narrow reserved-port fallback

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-28 20:28:39 -07:00
Brennan Benson af2972b3a9 fix(mobile): declare happy-dom so terminal-webview tests run standalone (#11238)
mobile/src/terminal/terminal-webview-{tap-routing,init-surface}.test.ts
request the happy-dom vitest environment, but happy-dom was only declared
at the repo root. The mobile suite resolved it by walking up into the root
node_modules, so `cd mobile && pnpm install && pnpm test` fails with
ERR_MODULE_NOT_FOUND and loses those 12 tests unless a root install
happens to be present.
2026-07-28 20:27:42 -07:00
OrcaWin 2d14651db5 docs(ssh): record exact artifact validation 2026-07-28 20:18:01 -07:00
OrcaWinandOrcaWin 76b6c137c6 fix(orchestration): sanitize legacy formatted JSON (#11263)
* fix(orchestration): sanitize legacy formatted JSON

* fix(orchestration): harden legacy message formatting

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-28 20:16:55 -07:00
OrcaWin 5611bb45b5 fix(ssh): close recovery ownership gaps 2026-07-28 20:13:37 -07:00
OrcaWin ddc60bf62b fix(ssh): bound main IPC output lifecycle 2026-07-28 20:13:37 -07:00
OrcaWin 879f502e7f docs(ssh): reconcile final backpressure lifecycle 2026-07-28 20:13:37 -07:00
OrcaWin 3c2d86c493 fix(ssh): secure relay endpoint credentials 2026-07-28 20:13:36 -07:00
OrcaWin 28ac1cc223 fix(relay): contain streaming edge failures 2026-07-28 20:13:36 -07:00
OrcaWin c5441a90c4 fix(ssh): close projection replacement races 2026-07-28 20:13:36 -07:00
OrcaWin 21f2e98c2a fix(ssh): type stale owner recovery mismatches 2026-07-28 20:13:36 -07:00
OrcaWin dc7672de41 test(ssh): register mux drain-priority coverage 2026-07-28 20:13:36 -07:00