Commit Graph

4383 Commits

Author SHA1 Message Date
hermes-seaeye[bot] 47104b9f17 fmt(js): npm run fix on merge (#106881)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-09 20:59:22 +00:00
Teknium 474143da81 fix(desktop): trim the auto Show-earlier gate and prove it end to end
Drop the injectable `topEdgePx` knob (one caller, one constant) so the
threshold has a single home, and fold the two-branch tail of
shouldAutoShowEarlier into one predicate. Comments keep the WHY: wheel is
the only signal at a clamped scrollTop 0, and the gates name the states
where scrollTop is near 0 without meaning "read earlier".

Tests: collapse the 11-case matrix into one invariant test on the pure
gate, and add a rendered-list test that mounts the real Thread against a
transcript heavier than RENDER_BUDGET, escapes the bottom lock, wheels up
at the clamped top and asserts more turn groups mount — while an upward
wheel mid-transcript mounts none. Red on origin/main (28 groups stay 28),
green with the fix.
2026-09-09 12:23:20 -07:00
KoNit-K 3ec8042483 fix(desktop): page earlier transcript on top-edge scroll
Long conversations and branched sessions hide older turns behind the
render budget, but only the Show earlier button loaded them. Auto-page
through the same showEarlier path when the reader is at the viewport top.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 12:23:20 -07:00
hermes-seaeye[bot] 15f3037647 fmt(js): npm run fix on merge (#106818)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-09 19:21:46 +00:00
Teknium 544169a41a perf(desktop): stop per-event full-timeline allocations on streamed tool events
Every tool.start / tool.progress / tool.complete ran three whole-timeline
passes that each allocated per part: `completeOpenStreamParts` (`map` with a
spread per row), the pending-row scan (`map` → `filter` → `map`, one wrapper
object per part), and `generatedImageEchoSources` (`flatMap` + a throwaway
array per non-image row) — then `dedupeGeneratedImageEchoesInParts` copied
the array again even when it stripped nothing, handing React a fresh
identity per event. Replace them with single in-place loops, and return the
input array from the dedupe when there are no echoes so a no-op stays a
no-op for the store.

Measured on the real `useMessageStream` hook (vitest/jsdom, 2,000 starts +
2,000 completions interleaved with text deltas): 618 ms → 138 ms; the
tool-only 2,000×2 case 252 ms → 67 ms. The remaining O(n) is one
`findIndex` per event over a ~4k array (~8 µs), well under the cost of the
copy React needs anyway, so no per-stream id index is introduced.

Behaviour is unchanged: same first-match `toolCallId` resolution, same
sparse/no-id contextual correlation, same ordering of parts.

Co-authored-by: Xipong <217837358+Xipong@users.noreply.github.com>
2026-09-09 12:13:14 -07:00
Xipong 7b639164cf fix(desktop): parallelize roster source requests 2026-09-09 12:13:14 -07:00
teknium1 a02a3b9808 test(desktop): trim MCP polling tests to two invariants per fix
Health checker: keep the 12-reconnect coalesce test and replace the
stop/disconnect drop rows with the invariant #106135 names — one fresh
follow-up still runs when the active sweep fails through the handled
config-error path (13 config reads on main, 2 after the fix). Log poller:
keep single-flight + resumed cadence, and shrink the replacement-lifecycle
test to "stop suppresses a late publish and leaves no timer". Both files
were run against origin/main source and failed (11 / 13 / 13 requests).
2026-09-09 12:11:25 -07:00
teknium1 8ac2c147a5 refactor(desktop): drop the serial-task layer from the MCP log poller
startCompletionPoll already keeps the pane single-flight and its stop
function already suppresses a late publish, so createSerialTask (a second
queue that never forwarded its AbortSignal to getLogs) and the AbortError
re-throw inside the poll callback were defense-in-depth on top of the fix.
A profile switch now tears the old lifecycle down and starts the new one
immediately; at most one stale read can still be in flight, and its result
is discarded. Replace the AbortController with a plain stopped flag.
2026-09-09 12:11:25 -07:00
Xipong d93988233c fix: serialize MCP log polling 2026-09-09 12:11:25 -07:00
Xipong 3fbc408ff6 fix: preserve event delivery across scheduler lifecycle boundaries 2026-09-09 12:11:25 -07:00
Xipong 42f2067092 fix(desktop): coalesce MCP reconnect sweeps 2026-09-09 12:11:25 -07:00
Teknium d31318745c test(desktop): trim salvaged fixtures to the invariant tests
Drop the two companion tests from #106124 and #106099 that already pass
on main without their fixes (a pinned active transcript is never measured
because prune() skips it before weighing; a rerendered effect already
discards the previous closure). Keep the one red-on-main invariant per
fix: no re-serialisation of unchanged warm transcripts, and change ticks
coalescing behind an in-flight session.active_list request.
2026-09-09 12:09:00 -07:00
Xipong 3d4c537584 fix(desktop): coalesce pending live status refreshes 2026-09-09 12:09:00 -07:00
Xipong 3bfd7b653b fix: cache warm transcript weights 2026-09-09 12:09:00 -07:00
Xipong 5a2ccffd41 fix(desktop): skip distro probe for WSL drive paths 2026-09-09 12:09:00 -07:00
Xipong ef677265d2 fix(desktop): reuse detected SSH platform 2026-09-09 12:09:00 -07:00
teknium1 fc36288832 fix(desktop): say "Auto-discovered" in the project row's accessible name
The salvaged glyph swap and hover tooltip are visual-only: the codicon is
aria-hidden and a Radix tooltip only speaks on hover/focus, so a screen
reader still announced an auto-discovered repo exactly like an explicit
project ("Open my-repo"). #106162 asks that the distinction reach
assistive tech and not rely on the glyph/color alone — the link's own
aria-label now appends the same "Auto-discovered" string for isAuto rows.

Also trims the salvaged tests from three to two invariants (explicit vs
auto), each asserting glyph, tooltip and accessible name together; the
"isAuto absent" case was a duplicate of the explicit case.

Refs #106162
Refs #73888
2026-09-09 10:58:43 -07:00
David Metcalfe 03b5460ad9 fix(desktop): distinguish auto-discovered repos from explicit projects in the sidebar overview
Auto-discovered repo lanes (Tier 2/3 in project_tree.py, tagged isAuto on
the tree node) rendered identically to explicit projects — both used the
folder-library lead glyph under the same PROJECTS header, so a user with
one real project could not tell it apart from repos Desktop found by
scanning disk.

The backend already emits isAuto; the frontend used it for sorting,
dismissal, and context-menu behavior but never surfaced it visually.
Auto nodes now render the repo glyph instead of folder-library, and the
row label carries an "Auto-discovered" tooltip. Explicit projects and
Home are unchanged. Adoption still works: theming an auto repo creates a
real projects.db row, the next tree refresh promotes it to an explicit
project (id p_<hex>, isAuto unset), and the auto cue disappears.

Closes #80383. Also addresses #73888 (same request, filed earlier).
2026-09-09 10:58:43 -07:00
Teknium c80003ff57 refactor(desktop): key the live-owner escape off the 4090 reason code, drop the prose sniffer
The gateway already ships the refusal as machine data: prompt.submit returns
JSON-RPC 4090 with error.data.reason = SESSION_NOT_OWNED. Matching the English
sentence in a new lib helper duplicated that contract and would silently miss a
reworded or localized message. submit.ts now classifies the rejection once
(isSessionNotOwnedError beside the sibling isSessionBusyError, reason code
first, prose only for pre-contract backends) and stamps the inline error bubble
with errorSurface {layer: gateway, code: SESSION_NOT_OWNED, retryable: false},
the same descriptor shape the gateway uses for turn failures. The error card
reads surface.code instead of re-sniffing text; retryable=false already hides
Retry through the existing gate.

Dropped from #106248: the stranded-resume overlay button. That overlay fires
when session.resume itself fails through every retry; reading is never fenced
by the lease, so the live-owner refusal never reaches it (the issue's screenshot
is the inline turn-error card). Also drops the resumeStartNewSession locale
keys and the separate detector module + its test; two invariant tests remain
(submit stamps the descriptor; the card offers Start new session / hides Retry).
2026-09-09 10:57:47 -07:00
KoNit-K 6efe3a45c1 fix(desktop): offer Start new session on live-owner exclusivity refusals
Retry alone is a dead end when another surface holds the session lease
(JSON-RPC 4090 / SESSION_NOT_OWNED). Surface an explicit new-session
escape on the turn-error card and the stranded-resume overlay without
weakening the lease guard.
2026-09-09 10:57:47 -07:00
teknium1 8d93081971 fix(desktop): stop flagging local/LAN auxiliary pins as stale
An aux task pinned to a private endpoint via `base_url` (a home Ollama
box at `byron.local`, a LAN IP, localhost) is the intended per-task
endpoint feature and can never bill a provider. The Settings → Model
banner still counted it as "still run on openai" forever and offered
"Reset all to main", which would wipe the working local setup; the
post-switch `stale_aux` report had the same blind spot; and the aux row
never showed the `base_url` the backend already sends, so the pin was
indistinguishable from a paid-provider pin.

- `GET /api/model/auxiliary` now stamps each task with `local_endpoint`,
  the verdict of the one canonical classifier
  (`agent/model_metadata.py::is_local_endpoint`) — no TS mirror of the
  private-range rules, so frontend and runtime cannot drift.
- Desktop: the persistent banner filter is the pure
  `staleAuxAssignments()` and skips `local_endpoint` pins; the pinned row
  appends ` · <base_url>` when one is set.
- `_stale_aux_pins` (post-switch report) skips local pins the same way.
- `is_local_endpoint`: `*.local` (RFC 6762 mDNS) now counts as local, and
  IPv6 literals no longer ride the "no dots ⇒ unqualified host" rule, so
  a global-scope address (`2607:f8b0::1`) is not local while `::1`,
  ULA and link-local still are via the `ipaddress` scope checks.

Slim redo of #106236 (@webtecnica) and #106234 (@huklaa), which fixed the
same symptom with a client-side classifier copy; the bug class, row
display and mDNS/IPv6 classifier corrections are theirs.

Refs #106228

Co-authored-by: Hukla <129692708+huklaa@users.noreply.github.com>
Co-authored-by: webtecnica <webtecnica@gmail.com>
2026-09-09 10:33:00 -07:00
Teknium e74c4a00ca Merge pull request #69446 from NousResearch/feat/plugin-catalog
feat: plugin catalog — curated SHA-pinned plugin index (CLI, admission CI, docs, dashboard)
2026-09-09 09:22:21 -07:00
kshitijk4poor 84e1761725 chore(desktop): drop in-repo repro script (ran git stash in the checkout) 2026-09-09 21:13:48 +05:30
finn763 b4ccbccf9c fix(desktop): calm background sessions Closes #73287 2026-09-09 21:13:48 +05:30
kshitijk4poor 0272647ad0 test(desktop): hud edge poll parks while the window is not viewed 2026-09-09 21:11:16 +05:30
finn763 b9fea361ec fix(desktop): stop idle renderer burn Closes #88275 2026-09-09 21:11:16 +05:30
hermes-seaeye[bot] ed2d821021 fmt(js): npm run fix on merge (#106554)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-09 12:04:16 +00:00
hermes-seaeye[bot] 9e7b4eca47 fmt(js): npm run fix on merge (#106539)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-09 11:58:08 +00:00
Teknium 0b8dbd5b66 fix(desktop/i18n): ru locale — agent plugins block follows the Capabilities move
main added the Russian locale after this branch narrowed settings.plugins.agent
to {title, movedToCapabilities, openCapabilities}; ru.ts still carried the
old per-plugin strings and failed typecheck.
2026-09-09 04:50:34 -07:00
kshitijk4poor 809654d488 refactor(desktop): keep main's boot classification and share the ws-URL guard
Follow-up to the renderer-dial retry:

- Drop the `!connectionDescriptorResolved` gate so `bootFailureIsRetryable()`
  still decides every failure main can see (#82679 contract preserved for
  post-descriptor mint failures); the renderer-owned dial is OR'd in as the
  one failure main cannot classify. The budget check now short-circuits
  before the IPC, as on main.
- Four booleans → one `stage` variable; the `isGatewayReauthRequired` term
  is dropped (reauth is raised before the dial stage, so it was unreachable).
- `isGatewayWebSocketUrl` moves to `apps/shared/src/json-rpc-gateway.ts`
  and `JsonRpcGatewayClient.connect()` uses it, so the hook's "valid dial"
  predicate cannot drift from what `connect()` accepts.
- Tests: keep the three that bind behaviour (first dial retries under a
  stale ready snapshot; invalid URL terminal; post-connect failure
  terminal), drop the four that pinned the removed gate or duplicated the
  existing #82679 bound test. 46/46 green; reverting the hook to main makes
  the retry test fail.
2026-09-09 17:19:36 +05:30
cervantesh 60e90ed6e7 fix(desktop): retry transient remote gateway boot dials 2026-09-09 17:19:36 +05:30
Teknium bcff0a920e test(cron): replace fail-closed drift tests with snapshot-as-pin invariants
The old tests encoded the skip (agent never constructed, [drift_skip] text,
alert-once bit). New invariants, proven red on origin/main: an unpinned job
with model_snapshot=A / provider_snapshot=P runs with AIAgent(model=A) and
resolve_runtime_provider(requested=P) after the global default moved to B/Q;
an explicit job pin still beats the snapshot; cron.model / cron.model_provider
still beat the snapshot; a legacy job without a snapshot follows the global
default. test_cron_drift_alert_once.py is deleted with the bit it tested; the
config-notice and impact-summary tests drop guard_enabled / model_drift_guard;
the desktop toast test asserts the informational copy.
2026-09-09 04:32:13 -07:00
Teknium 7e4d02fef5 fix(cron): unpinned jobs run on their creation-snapshot model instead of failing closed
A global model/provider change must never stop a cron job. The #44585 guard
raised [drift_skip] for every unpinned job whose provider_snapshot /
model_snapshot no longer matched the live global default, so one `hermes model`
switch silently killed whole fleets (reported by fastfinge, nitinthewiz,
Dr-ilies; 13 of 60 jobs on the project lead's box after
claude-fable-5 -> claude-fable-5.1).

The snapshot is now the job's effective pin: _load_cron_job_config prefers
job['model_snapshot'] over the global default and _resolve_job_runtime passes
job['provider_snapshot'] as `requested` when neither a per-job pin nor a
cron.model / cron.model_provider fleet default covers the axis. One INFO line
per differing axis tells the operator what the job is running on and how to
move it. Jobs without a snapshot (legacy records) still follow the global
default; the existing fallback chain still handles a snapshot provider that
fails to resolve.

Both goals of #44585 hold: no silent inherit of a paid default (the job runs on
what it was created under) and no outage. Owner decision (Teknium): "main agent
model changing should not stop crons from executing, ever".

Removed as unreachable: _check_model_drift, DRIFT_SKIP markers, the
drift_alerted alert-once bit (mark_drift_alerted + the _record_run_outcome pop),
the drift special-cases in _compose_run_delivery and
_summarize_cron_failure_for_delivery, cron_model_drift_guard_enabled and the
cron.model_drift_guard config key (v42 migration drops it from existing
configs). The PLUGIN-COMPAT clear_drift_alerted block is untouched (scheduled
revert).

The `hermes config set model.default` notice and the Desktop model-change toast
are reworded from "will fail closed / will be skipped" to "keep running on the
model they were created under"; the impact payload drops guard_enabled (all six
desktop locales updated).
2026-09-09 04:32:13 -07:00
Teknium d47adec28f Merge origin/main into feat/plugin-catalog
Python plugin CLI/loader/web/tui files taken from main wholesale; the
catalog layer is re-ported onto main's decomposed shapes in the
following commits. plugin_index.py removed (catalog is the sole
discovery system).
2026-09-09 04:15:27 -07:00
Teknium c2524736d1 fix(desktop): a late startup locale read never overrides the user's in-session pick
With the startup config read now retried (previous commits, #76158), a retry
can resolve after the user has already chosen a language in Settings and
would repaint the stale on-disk value over it. Mark the explicit pick in a
ref and let every retry outcome (success or exhausted fallback) defer to it.

Idea and guard from #105470 (webtecnica), folded onto the #76158 base.

Co-authored-by: webtecnica <webtecnica@gmail.com>
2026-09-09 03:52:03 -07:00
mengyuyuan 5353fc9b32 fix(desktop): bound i18n locale retries and keep English fallback
Address review feedback on the startup-race fix:

- Restore the permanent-failure contract: a rejected config load settles
  on English (DEFAULT_LOCALE) so the UI stays usable, as enforced by
  context.test.tsx.
- Bound the retry loop to MAX_LOCALE_RETRIES (10) at 3s intervals instead
  of retrying forever — permanent auth/backend failures reach a terminal
  English state instead of scheduling requests indefinitely.

Tests: add coverage for transient-failure recovery (first attempt rejects,
retry applies persisted display.language) and for bounded-budget exhaustion
(no retry fires after the budget is spent).
2026-09-09 03:52:03 -07:00
mengyuyuan 140eaf9e03 fix(desktop): retry i18n locale load after backend startup race
The renderer mounts before the desktop's own backend is ready, so the
first GET /api/config times out after 60s. The catch handler fell back
to DEFAULT_LOCALE (en) permanently, leaving the UI stuck in English
until a manual language switch, even when config.yaml persisted
display.language: zh.

Retry with a 3s delay on failure instead of locking the default locale.
The backend comes up, the next attempt succeeds, and the persisted
language takes effect. Cleanup clears the pending retry timer on unmount.
2026-09-09 03:52:03 -07:00
kshitijk4poor 7dc796463d fix(agent): a persisted /steer row survives the next prompt's alternation repair; typed for history
Both steer sites now build the row through one helper, prompt_builder.steer_user_row:
a role:user row with display_kind="steer" and no leading blank lines. The alternation
repair (_merge_consecutive_users) skips a steer-typed prev row, so a run that ended
right after a steered batch (Ctrl-C, interrupt) does not get the next real prompt
merged INTO the already-persisted steer row — which would have rewritten it in place
and re-broken live≠replay parity, the exact class this PR fixes.

TUI/desktop history projects the steer row as the user's own words instead of the
model-facing marker wrapper; 'steer' joins the display_kind union. The compression
anchor scan keeps its tool-row branch for transcripts persisted before this change and
its docstring says so.
2026-09-09 12:21:28 +05:30
hermes-seaeye[bot] 990473a79c fmt(js): npm run fix on merge (#106237)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-09 03:18:52 +00:00
brooklyn! 5255d7823e test(desktop): cover scrolling message counts and pane isolation 2026-09-08 22:13:30 -05:00
brooklyn! 342f76a7c2 feat(desktop): show messages below the thread viewport 2026-09-08 22:13:30 -05:00
hermes-seaeye[bot] 610576454a fmt(js): npm run fix on merge (#106231)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-09 03:02:27 +00:00
Gille 153da9569f fix(desktop): keep macOS HUD visible when inactive (#102573) 2026-09-08 22:57:02 -04:00
brooklyn! 9e8c1569b6 fix(desktop): keep pets and starmap animated without focus 2026-09-08 21:53:33 -05:00
brooklyn! 3cad0f312b fix(desktop): keep visible renderer animations running on blur 2026-09-08 21:53:33 -05:00
brooklyn! 72a3277cd7 test(desktop): cover project creation scope and reconnect routing 2026-09-08 20:05:07 -05:00
brooklyn! 2b6a5181a3 fix(desktop): allow project creation while browsing all profiles 2026-09-08 20:05:07 -05:00
brooklyn! 258b351fc1 fix(desktop): keep background reports behind bounded disclosures 2026-09-08 18:53:48 -05:00
brooklyn! 608b97c305 docs(desktop): document sidebar glass defaults 2026-09-08 18:45:42 -05:00
brooklyn! 1175ac4bb9 feat(desktop): default glass to 29% tint on the sidebar 2026-09-08 18:45:42 -05:00