Commit Graph

2760 Commits

Author SHA1 Message Date
Teknium 03c85a1c7c test(desktop): port gateway-file-download tests to vitest electron project
The salvaged branch predates the electron test project's node:test -> vitest
migration (test:desktop:platforms is now `vitest run --project electron`).
Import `test` from vitest so the suites are collected; assertions stay on
node:assert/strict per the existing electron test convention.
2026-08-15 00:37:00 -07:00
Paul Armbruster 27b003996a fix(desktop): stream native gateway downloads + 404 data-url fallback
Addresses both review findings on the remote-gateway download PR:

1. Unbounded buffering (finding #1). fetchBuffer / fetchBufferViaOauthSession
   accumulated the entire response (then copied it again via Buffer.concat)
   before saveGatewayFile even opened the save dialog, so a large gateway file
   could exhaust the native process. Both auth paths now stream: once response
   headers arrive the connect timeout is cleared, the filename is derived, the
   save dialog is shown, and the body is piped to the chosen destination with
   backpressure. A read/write error tears down the stream and unlinks the
   partial file. The byte-moving, data-URL decoding, and filename/path helpers
   are extracted into gateway-file-download.ts so they're unit-testable without
   Electron.

2. No fallback for older gateways (finding #2). saveGatewayFile required the new
   /api/fs/download route. Desktop and the remote gateway update independently,
   so a gateway predating this PR 404s. Added a 404-only compatibility fallback
   to the existing capped /api/fs/read-data-url route (bounded, so it only
   serves smaller files — enough to keep older backends working).

Tests: gateway-file-download.test.ts covers streaming, backpressure,
error-cleanup (unlink on write/response error), data-URL decoding, filename
derivation (incl. traversal reduction), and 404 detection;
gateway-file-download-transport.test.ts asserts both transports stream (no
whole-body Buffer.concat) and that the 404 fallback is wired. Both registered
in the desktop platform test list. Server-side /api/fs/download tests
(streaming + sensitive-file reject) already pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 00:37:00 -07:00
Paul BlackSwan d5a865882a fix(desktop): save remote gateway files natively 2026-08-15 00:37:00 -07:00
Dean Chen 0bc7070cf8 fix(desktop): gate image copy on decoded contents 2026-08-15 00:36:15 -07:00
Dean Chen 3c570c61ff fix(desktop): preserve copy image for large remote media 2026-08-15 00:36:15 -07:00
DannyFengTianYu 07b9090259 fix(desktop): preserve complete history when branching
Read the durable display transcript when creating a branch instead of copying the compacted model projection. Hydrate the Desktop branch boundary from persisted history, avoid stale whole-chat counts, and seed the new tile from the backend snapshot. Add regression coverage for compacted histories, visible-message counts, selected prefixes, and hydration races.

(cherry picked from commit c3d2d759ae104395adde215b2e293ccf8e895684)
2026-08-15 00:35:40 -07:00
mj 73e9b1a124 docs(desktop): align getGateway docstring with $gateway atom subscription
Residual hunk from PR #84832's picker-rebind commit; the functional
surfaces.tsx change landed on main via #86250.

(cherry picked from commit 990936d6f6e752ddb474787c1f7fdc3ad4f4dc60, docstring hunk only)
2026-08-15 00:35:28 -07:00
mj 20e5d78f54 fix(desktop): recover failed gateway catalogs
(cherry picked from commit ef1d2cc5e541f37fbd12b41f686a4c18a9b85320)
2026-08-15 00:35:28 -07:00
mj 1ef7a0bdda fix(desktop): recover empty model catalogs
(cherry picked from commit 72a06b9e0b392c494be011dc74ccf948ca693e3f)
2026-08-15 00:35:28 -07:00
Teknium ddf6456794 test: stub virtualizer.measure in virtual-session-list mock (density invalidation call) 2026-08-15 00:34:57 -07:00
Teknium 29c28136fe style(desktop): prettier/eslint pass on salvaged files 2026-08-15 00:34:57 -07:00
Teknium 865e4e7178 fix(desktop): default session list density to compact
Keeps the sidebar byte-identical for existing users until they opt into
comfortable/detailed themselves — per the no-default-change salvage rule.
2026-08-15 00:34:57 -07:00
David Metcalfe a33ac1e95e feat(desktop): add session list density modes
Squashed from #68124, resolved against current main (inbox cards, marquee
titles, actions cluster). Adds a Settings → Appearance 'Session list
density' preference — compact (default, unchanged), comfortable
(+deterministic metadata line), detailed (+initial-request preview) —
with density-aware virtualizer estimates.

Closes #68119

(cherry picked from commits 93bc19400a..568f78a8fc)
2026-08-15 00:34:57 -07:00
chillerno1 c6ec8a95e6 test(desktop): cover docked zone chevron direction
(cherry picked from commit 95358428f95175ef57376da334b69279618cce6a)
2026-08-15 00:34:57 -07:00
chillerno1 ac0d0f8897 fix(desktop): flip zone collapse chevron to action direction
Collapsed tool zones (terminal/logs) kept a down chevron after minimize,
so the restore affordance looked like another collapse. Point the icon in
the action direction — down when expanded, up when collapsed — matching
master-detail collapsible detail headers. Same fix for floating panes.

(cherry picked from commit 3392aeb9dba0ed9e9cabfb96b6010e46fa453ca1)
2026-08-15 00:34:57 -07:00
Isak du Plessis 10bf145e30 feat(desktop): collapse thinking by default
(cherry picked from commit 14fd1aace2c76c31f3b48e4b9bf9943c74582d21)
2026-08-15 00:34:57 -07:00
Teknium 15e7c563b3 style: eslint --fix on salvaged IME test (curly + padding rules) 2026-08-15 00:33:40 -07:00
Teknium aa95939469 test(desktop): cover IME composition guards in keybind combo resolution 2026-08-15 00:33:40 -07:00
Teknium 587788405a fix(desktop): extend stale-flag self-heal and keyCode 229 guard to the edit composer
Widen the composer-side IME fixes to the inline edit composer: the same
missed-compositionend wedge and post-compositionend keyCode 229 Enter
apply to its handleKeyDown path.
2026-08-15 00:33:40 -07:00
BAS Lam 9f2e6d05ab fix(desktop): ignore IME composition keydowns in keybind combo resolution
Chinese/Japanese/Korean IMEs emit keydown events during composition that
carry preedit keystrokes and the commit keypress (Enter/Space/Shift for
candidate selection). Treating them as combos fires unrelated keybinds —
e.g. typing 你 with a CJK IME could dispatch session.new and silently
open a new session.

Guard comboFromEvent():
- Bail out entirely while composing (event.isComposing or key === 'Process')
- Ignore keydowns whose event.key is a bare modifier name but whose code
  is a regular key — legacy IMEs that synthesize keystrokes (Q9 2002 sends
  key="Control" with code="KeyW") would otherwise canonicalize into
  phantom combos like mod+w that close the active tab.

Tested with Q9 (九方) legacy IME on Windows.
2026-08-15 00:33:40 -07:00
AIalliAI 39e7607794 fix(desktop): recover from a stale IME composition flag that wedged the composer
A missed compositionend (focus jump, input-source switch, programmatic DOM
swap mid-preedit) left composingRef stuck true, and the stuck flag silently
swallowed every Enter in handleEditorKeyDown and every Send-button submit via
the form onSubmit guard — no error, no RPC, until the composer remounted. For
CJK IME users (where even ASCII typing runs through composition) this read as
"Enter has no effect; messages cannot be sent", degrading composer instance
by composer instance.

Recover in two places, both grounded in invariants Chromium guarantees:

- keydown: every keydown during a genuine composition carries
  isComposing=true, so when the native flag says we're not composing, clear
  the stale ref before the guard reads it.
- blur: a composition never survives focus loss, so clear the flag
  unconditionally — this is what unblocks the Send button path, which has no
  native composition flag to consult.

The genuine-IME protection (#37483 class) is untouched: Enter with
isComposing=true is still swallowed.

Fixes #44135
2026-08-15 00:33:40 -07:00
liyunlong 202e5b813a fix(desktop): block Enter keyCode 229 after IME compositionend
macOS Chinese IME (and some 3rd-party Windows IMEs) emit Enter with
keyCode 229 (legacy VK_PROCESSKEY) after compositionend, while
isComposing is already false. The existing guard only checked
isComposing and the composingRef, so this Enter slipped through and
submitted the message before the committed text was fully in the DOM.

Add an explicit keyCode 229 check in handleEditorKeyDown.  keyCode is
deprecated, but it is the only reliable signal for this IME commit
Enter on Chromium-based browsers.  Includes a dom-repro test that
simulates the macOS IME sequence.

Fixes: "中文混英文按 Enter 直接上屏"
2026-08-15 00:33:40 -07:00
izumi0uu 3d51c40997 fix(desktop): prevent IME submit in inline edit composer 2026-08-15 00:33:40 -07:00
Takumi Sato 20beee774c fix(desktop): prevent Enter from submitting during IME composition
Check event.nativeEvent.isComposing in Enter-to-submit branches so CJK (Japanese, Chinese, Korean) and other compositional input methods commit the candidate instead of firing the send handler.

Applied to all five sites in the desktop renderer that currently handle Enter with no composition guard:

- chat composer main submit and trigger popover (apps/desktop/src/app/chat/composer/index.tsx)
- message edit composer submit and trigger popover (apps/desktop/src/components/assistant-ui/thread.tsx)
- onboarding API key and auth code inputs (apps/desktop/src/components/desktop-onboarding-overlay.tsx)
- session rename input (apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx)

Fixes #37483
2026-08-15 00:33:40 -07:00
Teknium 1025cb0e66 fix(desktop): stop deleted sessions resurrecting through racing list fetches
A session deleted in the sidebar disappeared optimistically but flashed
back when any list fetch raced the in-flight DELETE RPC — the backend
page still carried the doomed row until the transaction committed
(#50928, reproduced with 'Load more' and auto-refresh). The optimistic
tombstone ($removedSessionIds) was only honored by the recents slice of
refreshSessions; the messaging slice, the per-platform pager, and
refreshMessagingSessions ingested backend pages unfiltered.

Extract the tombstone filter into dropTombstoned() and apply it at every
session-list ingestion point. Tombstones only exist while a delete or
archive is in flight (they self-clear on confirmation and are removed
immediately on failure), so non-destructive refresh paths are untouched.

Fixes #50928
2026-08-15 00:33:32 -07:00
Gorde Minchel e6708af1f2 feat(desktop): confirm before deleting a session
Deleting a session in the desktop app fired instantly on click — the CLI
path (hermes sessions delete) asks y/N by default, so one misclick (Archive
and Delete sit right next to each other) permanently destroyed a conversation
with no dialog and no undo (#61470).

Route every delete entry point (sidebar rows, tab menus, the chat header,
context menus — all share useSessionActions) through a shared
DeleteSessionDialog built on ConfirmDialog. ConfirmDialog gains an
onOpenAutoFocus prop so dialogs with no input keep focus off the close
button (a11y).

Tests: menu delete now asks; cancel keeps the session; Enter confirms;
Escape cancels; delete item disabled without onDelete; the same guard
applies via SessionContextMenu.
2026-08-15 00:33:32 -07:00
PRATHAMESH75 8e35ff0a62 fix(desktop): confirm before clearing the entire Enabled Toolsets list (#73319)
Config settings auto-save on a 550ms debounce with no undo. The
'Enabled Toolsets' list is rendered by the generic ConfigField with no
destructive-change guard, so a stray select-all + Backspace (or any edit
that empties the list) is persisted the moment Settings closes —
silently disabling memory, terminal, web search, delegation, and most
tools. Recovery required CLI intervention.

Guard the one destructive transition: when the enabled-toolsets list
goes from non-empty to empty, window.confirm() before applying it (the
same pattern env-var removal already uses in toolset-config-panel.tsx).
Every other edit passes through untouched.

The decision is a pure helper (clearsEnabledToolsets) so it is unit
tested directly rather than through a full settings render. New i18n key
toolsetsWipeConfirm added to en + zh; partial locales inherit the
English string via defineLocale fallback.

Fixes #73319
2026-08-15 00:33:32 -07:00
Teknium 6b39ae490d fix(desktop): let sidebar wheel scrolling chain past the virtualized list
With 25+ sessions the recents list virtualizes into its own nested
scroller inside the sidebar's scroll container. Both carried
overscroll-contain, so once the inner scroller hit a scroll boundary the
wheel gesture was consumed instead of chaining to the outer sidebar
scroller — read as a mid-list wheel dead-zone while scrollbar drag kept
working. Drop the containment on the inner scroller only; the outer
sidebar scroller keeps overscroll-contain so the gesture still never
escapes the sidebar.

Fixes #84964
2026-08-15 00:33:22 -07:00
Inspired-atm db1d8983e5 fix(desktop): no-op branch switch on non-repo project lanes
Non-repo explicit projects (plain folders) get a main-checkout lane whose
label is the folder basename, not a branch. Clicking "+" (new session) on
such a lane calls switchBranchInRepo -> switchBranch, which sanitizes the
basename to "" and throws "Branch name is required.", aborting the
session creation. Short-circuit switchBranch for roots that are not git
work trees so callers proceed with a plain session.

Fixes #83028
2026-08-15 00:33:11 -07:00
Mustafa d40dda7db6 fix(desktop): preserve lane session recency order 2026-08-15 00:33:11 -07:00
liuhao1024 3defb25a42 fix(desktop): evict stale lane entry when overlay moves session to worktree
When a session's cwd moves from the main checkout to a newly created
worktree, overlayRepoLanes places it into the matching worktree lane but
never removed the stale entry from the main lane. The session appeared
under both groups until the user left and re-entered the project view.

Add a cross-lane eviction loop that removes the session from all other
lanes before inserting it into the target lane.
2026-08-15 00:33:11 -07:00
rsk-731 0fd223d1bd fix(desktop): stop dual main lanes for non-git project workspaces
Live overlay always placed sessions under `::branch::main`, while the
backend non-git heuristic keys the lane by folder path (label =
basename). Overlay missed that lane by id/label and forked a phantom
`main` group with the same sessions.

Match existing path-keyed isMain lanes before creating a branch-style
main lane. Covers the codex-research-guardian-style drill-in duplicate.
2026-08-15 00:33:11 -07:00
Teknium aa8e92f516 test: pin document.hasFocus in status timer test (useViewedInterval gating) 2026-08-15 00:32:53 -07:00
Teknium f535a4f284 test(desktop): pin document.hasFocus in background-sync backstop tests 2026-08-15 00:32:53 -07:00
AlexDev_ bee0d45ddf perf(desktop): pause background UI work while unfocused 2026-08-15 00:32:53 -07:00
Teknium 4f29374662 fix(gateway): send-once spritesheet semantics for pet.info (#54730)
pet.info accepts knownRevision; when it matches the active sheet's
revision the multi-MB spritesheetBase64 is elided and
spritesheetUnchanged=true is returned. The desktop floating pet passes
the revision it already holds and keeps its cached bytes, so backstop
refreshes no longer resend ~3.2MB frames over the WS (write-loop stalls,
disconnect storms). Legacy callers omitting knownRevision get the full
payload unchanged.
2026-08-15 00:32:44 -07:00
Lavínia Beghini 28d2de1845 fix(desktop): surface isolated tool failures in pet state 2026-08-15 00:32:44 -07:00
Adolanium 5eb1d2b0aa fix(desktop): restore pet.info backstop after live-sync regression
Event-capable backends no longer polled pet.info, so a cold-start
fail-open enabled:false left the mascot hidden until Settings re-seeded
the store. Keep a slow backstop and short startup retries.
2026-08-15 00:32:44 -07:00
fanfan343 d474ba5615 test(desktop): cover bicubic smoothing in pet sprite 2026-08-15 00:32:44 -07:00
fanfan343 fd0872214b fix(desktop): smooth-scale pet sprite frames
Petdex spritesheets are 192x208px illustration frames, not pixel art.
The desktop canvas drew them with imageSmoothingEnabled=false
(nearest-neighbour), so zoomed pets looked blocky. Enable bicubic
smoothing so scaled frames stay clean at any zoom.

High-DPI backing-store sizing is addressed separately in #83276.
2026-08-15 00:32:44 -07:00
NorethSea 34e05c32ec fix(desktop): render pet sprite at devicePixelRatio for sharp HiDPI output
DPR-sized canvas backing store separated from CSS footprint, tracking
zoom/display changes live. Rendering-fix subset of PR #75307; the
overlay-placement feature portion is out of scope here.

Covers the devicePixelRatio half of #83216.
2026-08-15 00:32:44 -07:00
thatssoheil bceac696ae fix(desktop): artifacts page timestamps render 1970 and local images fail
All three artifact timestamp sources (message.timestamp,
session.last_active, session.started_at) are epoch SECONDS — the
transcript reader and session-date-groups both multiply by 1000 — but
the collector passed them straight to new Date() (ms), so every
artifact rendered as 1970-01-21. Normalize seconds to ms once at
collection; the Date.now() fallback stays ms.

Local file artifacts (e.g. D:\ComfyUI\output\*.png) fell through to
mediaExternalUrl() which yields a file:// URL the renderer cannot
load. Route through the desktop fs bridge whenever it exists —
readDesktopFileDataUrl already dispatches remote REST vs local
Electron internally (#83380).
2026-08-15 00:32:35 -07:00
Johnny Silverhand 021950ac81 fix(desktop): stop artifact over-indexing
Require explicit provenance for tool-result artifacts while preserving assistant links, MEDIA deliveries, generated outputs, file mutations, and browser screenshots. Normalize persisted Unix-second timestamps at collection time and retain millisecond fallbacks.

Consolidates current-main-compatible work from #41156 and #48577.

Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>

Co-authored-by: tt-a1i <53142663+tt-a1i@users.noreply.github.com>
2026-08-15 00:32:35 -07:00
icemeng 96d6db1993 fix(artifacts): convert DB timestamps from seconds to ms for Date()
The Artifacts view reads message.timestamp, session.last_active, and
session.started_at from the SQLite database, which stores all timestamps
as Unix epoch seconds (REAL). These values were passed directly to
JavaScript's Date() constructor, which expects milliseconds — causing
every artifact timestamp to display as January 1970 dates.

Fix by multiplying the database value by 1000 to convert seconds to
milliseconds at the storage point, using nullish coalescing (??) instead
of logical OR (||) so that valid zero timestamps are not skipped.
2026-08-15 00:32:35 -07:00
hermes-seaeye[bot] 0807673e1f fmt(js): npm run fix on merge (#86751)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-15 06:22:02 +00:00
Teknium 0904f50e3e fix(desktop): address connections-registry review findings
Review fixes from #86679 comments (trevorgordon981, helix4u, kshitijk4poor):

- Edit inheritance: mergeConnectionInput preserves fields the editor does
  not carry (cloud org, ssh remoteHermesPath/remoteProfile) so a rename no
  longer wipes them. When the payload carries an ssh host string, stored
  user/port are NOT inherited — the composite host field is authoritative,
  fixing the stale user/port resurrection on edit.
- Token hygiene: tokens only persist on token-auth remotes; switching an
  entry to oauth (or cloud) clears the stale envelope.
- Plain-text opt-in: the panel now surfaces the same consent dialog as
  Settings -> Gateway on keyring-less machines (registry list exposes
  secureTokenStorage; save retries with allowPlainTextToken after consent).
- Registry test isolation: hermes:connections:test builds the probe directly
  from the registry entry instead of coercing against v1 connection.json —
  no more inheriting the v1 global token for a different host, and the local
  entry now probes the app-managed backend (never v1 remote/ssh state, so
  the test button can no longer trigger a v1 file write).
- 'local' id reserved at the validation boundary: a crafted IPC payload can
  no longer replace the local entry via upsert.
- Cloud creation hidden in the editor (a dialable cloud entry comes from the
  Cloud sign-in/discovery flow); migrated cloud entries stay editable.
- First-run migration write is guarded: a failed write keeps the migrated
  registry in memory instead of hard-failing every connections IPC call.
- uniqueLabel(): single label-dedup helper — counts up instead of "X 2 2",
  clamps 253-char migrated URL-host labels under LABEL_MAX; used by
  normalizeRegistry and both migration paths.
- UI copy: staged-rollout note replaces the "side by side" claim; test
  failure toast leads with the failure wording; dropped unused i18n keys.

Tests: +9 pure-module cases (reserved id, token-drop rules, merge
inheritance, ssh host precedence, uniqueLabel); electron+settings suites
1355 passed.
2026-08-14 23:06:23 -07:00
Teknium b54b0521dc feat(desktop): multi-connection registry — named agent sources (schema v2 + IPC + Settings UI)
First slice of multi-source agent support: the desktop can now persist ANY
number of named backends (local runtime, remote gateways, Hermes Cloud
instances, SSH hosts) side by side instead of one global connection plus
per-profile overrides.

- electron/connection-registry.ts: pure v2 registry module — required
  case-insensitively-unique labels (device names), @name-device handle rule
  for duplicate profile names across sources (agentHandle), defensive
  normalizeRegistry for corrupt files, one-time v1→v2 migration that imports
  the global block + per-profile overrides (deduped by URL/host) and leaves
  connection.json untouched for older builds.
- main.ts: connections.json storage beside connection.json (same secret
  posture: safeStorage-encrypted tokens, 0600, tighten-before-parse, mtime
  cache) + hermes:connections:* IPC (list/save/remove/set-primary/test).
  Test maps registry entries onto the existing testDesktopConnectionConfig
  probe stack — no new probe code.
- Settings → Connections: manage the registry (add/edit/remove/test/make
  primary) with forced naming; local entry is non-removable; removing the
  primary retargets to local. en + zh locales.

Storage-level only by design: routing/pool generalization to composite
(connection, profile) keys, the multi-source roster, plugin SDK surface, and
fan-out updates land as follow-up PRs.
2026-08-14 23:06:23 -07:00
Teknium 2a9f57c505 fix(desktop): suppress skill suggestion pill when the session already touched the skill
The composer's "Use skill: X" pill only checked the draft text and the
workspace-name collision - it happily re-offered a skill the session had
already loaded (skill_view), edited (skill_manage), or that the user had
invoked via its /name command. Clicking it would re-inject the full
SKILL.md into a context that already carries it.

The draft provider now scans the session transcript (per-runtime
$sessionStates mirror, $messages for the active session) for
skill_view/skill_manage tool calls naming the skill - exact or qualified
(category/name, plugin:name), from parsed args or hydrated argsText -
and for user turns starting with the skill's slash command, and stands
down on a hit. The scan only runs when the draft actually matched a
skill, so ordinary typing pays nothing.
2026-08-14 22:30:33 -07:00
Teknium b9e03b4d55 fix(desktop): give chat images native right-click and proper save filenames
Three fixes for generated/displayed images in the desktop chat:

- Shell fallback context menu no longer swallows right-clicks on images:
  the guard now yields to Electron's native image menu (Copy Image, Copy
  Image Address, Save Image As...) for img/picture/video/canvas targets,
  matching the existing editable/selection carve-outs.

- Save Image As / download button: generated-image URLs (fal.media etc.)
  end in an extensionless content hash, so saves produced an unopenable
  "All Files" blob. The main-process save dialog and the renderer anchor
  fallback both now append a MIME-derived extension, add image type
  filters, and default to the user's Downloads directory instead of the
  process cwd (win-unpacked on packaged Windows installs).

- New will-download handler routes any Chromium-initiated download
  through the same Downloads-dir + guaranteed-extension policy.

Validation: new unit tests for the filename derivation (6 passing);
npm run check:lint green (tsc x3 + eslint, 0 errors).
2026-08-14 22:29:45 -07:00
Teknium ac2e4207c1 fix(desktop): confirm detached updater spawn before quitting for hand-off (#66753)
The update hand-off spawned the detached updater, called unref(), and
quit unconditionally after the 2.5s dwell. Node reports exec failures
(ENOENT/EACCES) asynchronously via the child 'error' event, and a
short-lived updater can die inside that window — in both cases the app
vanished with no updater, no relaunch, and no evidence (the reported
macOS incident, and the posix.sh early-death reports on the same
thread).

Add observeUpdaterHandoff(): watch the just-spawned child for 'error'
and early 'exit' during the existing dwell (no added latency — the
dwell doubles as the settle window). Clean exit 0 inside the window
stays a success (the Windows `cmd start` wrapper exits immediately by
design); a spawn error, non-zero exit, or signal death is a failed
hand-off. On failure:

- applyUpdates (Windows hand-off): don't quit — restart the backend and
  surface a structured error to the UI.
- applyUpdatesPosixHandoff (mac/linux): don't quit — surface the error.
- handOffWindowsBootstrapRecovery: return false so the caller falls
  through to its next recovery path instead of quitting into nothing.

The pre-written update marker names the dead child pid, so
readLiveUpdateMarker self-heals it; no marker cleanup needed. Children
without an event interface settle ok after the window, keeping the
observation a best-effort hardening rather than a new way to wedge an
update.

Covered by 7 new unit tests (spawn-error, non-zero exit, signal death,
clean exit 0 wrapper, survival, double-settle, event-less child).

Closes #66753
2026-08-14 21:59:29 -07:00