Commit graph

18505 commits

Author SHA1 Message Date
Brooklyn Nicholson
5dadacfce7 docs(desktop): ban Close-X tips and click-to chrome lectures
Extend the tip rule past kebabs — no tip on dismiss X, and no tips that
only paraphrase a visible label or say "click to…".
2026-07-27 22:03:37 -05:00
Brooklyn Nicholson
1910d613ee test(desktop): assert kebabs are not wrapped in Tip
Flip the #67500 structure tests that required tooltip-trigger on menu
kebabs; keep coverage that the menus still open on click.
2026-07-27 21:43:57 -05:00
Brooklyn Nicholson
26413bbf57 fix(desktop): stop tip-wrapping kebab menu triggers
Remove the ActionsMenu tooltip prop and Tip wrappers on session, project,
workspace, panel, and credential ⋯ menus. aria-label stays for a11y; drop
the unused credentialActions string.
2026-07-27 21:43:57 -05:00
Brooklyn Nicholson
58714e0e2a docs(desktop): tip only when hover teaches something new
Drop the blanket "every icon* button needs a Tip" rule — it produced
tautological kebab tips like "Actions for <row title>". Menu triggers keep
aria-label; tips stay for unlabeled discovery chrome and keybind hints.
2026-07-27 21:43:56 -05:00
brooklyn!
84858d76ba
Merge pull request #73054 from NousResearch/bb/codeblock-chromeless
Chromeless markdown code blocks
2026-07-27 21:42:51 -05:00
brooklyn!
dc7414b1f2
Merge pull request #73045 from NousResearch/bb/composer-at-leading-slash
Let `@/foo` mean the same as `@foo` in the composer
2026-07-27 21:37:44 -05:00
Jeffrey Quesnelle
6cf572c9e5
Merge pull request #73053 from NousResearch/revert-67607-feat/hermes-relay-shared-metrics
Revert "feat(observability): integrate NeMo Relay runtime and shared metrics"
2026-07-27 22:28:55 -04:00
Jeffrey Quesnelle
841a5a744a
Revert "feat(observability): integrate NeMo Relay runtime and shared metrics" 2026-07-27 22:28:08 -04:00
Brooklyn Nicholson
b3174092f6 chore(desktop): drop the now-unused assistant.tool.code string
The code-block header was its only consumer.
2026-07-27 21:28:00 -05:00
Brooklyn Nicholson
39bde52014 fix(desktop): let the expandable fade match its own surface
The overflow fade hardcoded --ui-chat-surface-background, so inside a code
block it smeared the chat background over the block's own fill. Read an
--expandable-fade-from override with the chat surface as the default.
2026-07-27 21:28:00 -05:00
Brooklyn Nicholson
b7fc36cf3c feat(desktop): strip the header chrome off markdown code blocks
A fenced block carried a bordered card with a "Code · <lang>" title row and a
pinned copy button, which read as an attached artifact next to the reply. Drop
the border and the header: the block is now a tinted slab on --ui-bg-editor
with syntax highlighting and a copy button that reveals on hover. The streaming
glow moves entirely to box-shadow since there is no border left to animate.
2026-07-27 21:28:00 -05:00
Hermes Agent
5e88745f12 fix: exempt codex_responses from the empty-assistant-content pads
Commentary-phase Codex turns persist with content:'' by design (their
text is delivered via the interim assistant callback), and the Responses
wire has no 'assistant must not be empty' validation — padding them
broke test_run_conversation_codex_continues_after_commentary_phase_message
in CI. Both the builder pad and the send-time pad now skip
api_mode=codex_responses. Keying on the ACTIVE api_mode preserves the
repair for codex-written sessions replayed through a strict
chat-completions provider.
2026-07-27 19:23:34 -07:00
Hermes Agent
25a8574592 test: adapt multimodal pad-safety test to main's assistant-content flattening
Current main flattens multimodal assistant list-content to a plain string
before the send boundary, so the original assertion (list survives to the
wire) can't hold on this base. The test now asserts the load-bearing
contract instead: no crash, and the assistant turn's text is neither
dropped nor replaced by the pad. Adds a direct unit-shape check that the
pad predicate skips list content (the exact AttributeError shape from the
original bug) and pads only textless string turns.
2026-07-27 19:23:34 -07:00
0xprincess
2a26be69c9 fix: send-time pad must skip multimodal (list) assistant content
The empty-content pad loop called .strip() on am.get("content") without
checking the type.  Multimodal assistant turns carry content as a list
of parts (text/image), so a session with any image-bearing turn crashed
during request assembly:

    AttributeError: 'list' object has no attribute 'strip'

Guard with isinstance(content, str) — a multimodal turn is never the
empty textless shape the pad repairs.  Adds a regression test driving a
multimodal history through the loop.
2026-07-27 19:23:34 -07:00
0xprincess
309f06b044 fix: prevent session poisoning from empty partial-stream-stub assistant turns
A mid-tool-call stream drop with no delivered text produces a
partial-stream stub carrying content:'' and tool_calls=None.  The
conversation loop's truncation path appended it to history as
{"role":"assistant","content":""} before the continuation nudge, and
strict providers (Moonshot/Kimi via OpenRouter) reject empty assistant
content with HTTP 400 ("the message at position N with role 'assistant'
must not be empty") on the next replay.  Because the message is
persisted, every subsequent turn re-failed — the session was
unrecoverable.

Three layers, smallest blast radius first:

1. conversation_loop (length path): an EMPTY partial-stream stub is no
   longer appended as an interim assistant message; only the
   continuation user-message is.  Stubs that delivered partial text are
   still persisted so continuation stitching is unchanged.

2. chat_completion_helpers.build_assistant_message: never serialize a
   textless assistant turn with content:'' — pad to a single space, the
   same trick as the reasoning_content pad (#15250, #17400).  Tool-call
   turns are exempt (content:'' alongside tool_calls is accepted
   everywhere).

3. conversation_loop send boundary: pad a textless assistant turn's
   empty content to a single space AFTER all content-mutating passes
   (surrogate sanitize, whitespace normalization, thinking-only drops),
   before token estimation.  This is the durable repair for sessions
   ALREADY poisoned by older builds: the persisted stub rows are rebuilt
   to '' on every reload (_rows_to_conversation strips whitespace, so a
   DB-side pad can't survive) and only a send-time pad repairs them.

Verified: 485 tests pass across the four affected files; live replay of
a real poisoned session's resumed history against Moonshot via
OpenRouter returns HTTP 200 (was HTTP 400).
2026-07-27 19:23:34 -07:00
Brooklyn Nicholson
10d4975c2e chore: trigger CI 2026-07-27 21:10:55 -05:00
brooklyn!
fa7b0fcf5d
Merge pull request #73035 from NousResearch/bb/clarify-composer-enter
fix(desktop): let a typed message answer past a clarify prompt
2026-07-27 21:09:16 -05:00
Brooklyn Nicholson
044cf46a0d fix(gateway): treat a leading @/ as a separator, not just an absolute path
`@/Desktop` returned nothing while `@Desktop` worked. The leading slash
was always read literally, so the lookup went to the absolute `/Desktop`
— which doesn't exist — and dead-ended instead of finding the folder one
level down.

The `@` has already announced "this is a path", so the slash people type
next reads as a separator out of habit rather than a filesystem root.
Take the absolute meaning only when it resolves: the parent directory has
to exist, and a partially-typed segment has to match something in it.
Otherwise drop the slash and resolve from the cwd.

Real absolute paths are unaffected — `/usr`, `/etc/hos`, `/Users/...` all
pass the existence probe and keep their current behaviour. The guard
matters: stripping the slash unconditionally would let a repo-local
`etc/` shadow the real `/etc`.
2026-07-27 21:07:49 -05:00
brooklyn!
cfd79e5b1b
Merge pull request #73038 from NousResearch/bb/palette-active-theme
fix(desktop): show a check on the active theme and color mode in ⌘K
2026-07-27 21:07:21 -05:00
brooklyn!
7100e8d539
Merge pull request #73041 from NousResearch/bb/tab-strip-scroll-into-view
fix(desktop): keep the active tab and the "+" in view when the tab strip scrolls
2026-07-27 21:04:26 -05:00
Jeffrey Quesnelle
1470022ad8
Merge pull request #67607 from afourniernv/feat/hermes-relay-shared-metrics
feat(observability): integrate NeMo Relay runtime and shared metrics
2026-07-27 22:04:12 -04:00
brooklyn!
b6244959a6
Merge pull request #73040 from NousResearch/bb/context-menu-parity-2
Right-click parity for cron, webhooks, and profile rows
2026-07-27 21:03:32 -05:00
brooklyn!
3d649f3376
Merge pull request #73024 from NousResearch/bb/desktop-cold-start
perf(desktop): cut renderer cold start by keeping shiki/mermaid off the boot path
2026-07-27 21:03:08 -05:00
Brooklyn Nicholson
dfe3a23a60 fix(desktop): scroll the tab strip so a new tab and the "+" stay in view
Opening a tab in a zone with more tabs than fit appended it past the right
edge of the scrolling strip — the new tab and the "+" that created it were
both off-screen, so a second new tab meant scrolling back by hand. Activating
a tab from a keybind had the same problem in the other direction.

The zone header now scrolls its active tab into view on activation, and
scrolls all the way to the end when that tab is the last one so the trailing
"+" comes with it.
2026-07-27 20:57:32 -05:00
Brooklyn Nicholson
9b84c6a046 feat(desktop): right-click actions on the sidebar cron rows
The sidebar cron rows exposed only hover trigger/manage buttons and no
right-click. Add a context menu — trigger now, pause/resume, manage, and
delete — driven against the shared $cronJobs atom so the sidebar and cron
overlay stay in sync.
2026-07-27 20:56:57 -05:00
Brooklyn Nicholson
48368bc4a7 feat(desktop): right-click parity for every panel list row
Teach PanelListRow a menuItems prop that renders BOTH the hover kebab and a
matching right-click menu from one PanelMenuItem[] (via the shared actions-menu
primitive), so a panel row's two menus can't drift. Migrate the cron, webhooks,
and profiles panels onto it — right-clicking a row now opens the same
edit/delete/enable actions as its kebab.
2026-07-27 20:56:48 -05:00
Brooklyn Nicholson
69f72f2b7c fix(desktop): show a check on the active theme and color mode in ⌘K
The theme picker computed `active` per row but nothing rendered it — the
`active?: boolean` field and its trailing check were stripped in 8f73d0d94
and the computation was left behind, so the palette offered no way to tell
which skin was already applied. That matters more than it sounds: the
desktop persists its own skin in localStorage and only seeds (never
applies) the backend's `skin:` at connect, so config and window can
legitimately disagree.

Restore the field and render a trailing check, and mark the root-search
theme rows and both color-mode lists the same way so every appearance
picker in the palette reports its own current value.
2026-07-27 20:56:03 -05:00
Brooklyn Nicholson
c0dd6e1f3f fix(desktop): stop a clarify card swallowing keys it doesn't bind
The card marked itself as owning every printable key, so the first letter
of a typed-out answer vanished and the composer never focused. It now
publishes its row count and yields only Enter plus the 1..N+1 / A.. rows
it actually renders; everything else reaches the composer, which skips
the question on send.
2026-07-27 20:50:32 -05:00
Brooklyn Nicholson
251b668f4d fix(desktop): let the composer answer past a clarify card
Typing a real message instead of picking an option left the agent parked:
the clarify blocks inside its tool batch waiting on clarify.respond, so a
follow-up routed through steer/queue sat undelivered until the 5-minute
clarify timeout. skipClarifyRequest answers the parked question with the
same empty answer the card's own Skip button sends, so the tool returns
and the turn carries on with the user's words.
2026-07-27 20:50:32 -05:00
Brooklyn Nicholson
1baea21cfb perf(desktop): load shiki and @streamdown/code on first use, not at boot
The chunk split only helps if nothing on the entry graph statically
imports the heavy libs. Three seams did:

- react-shiki: now behind one lazy() boundary (shiki-block.tsx is its only
  static importer; LazyShiki wraps it with a PlainCode fallback).
- diff-lines.tsx: useShikiHighlighter hook extracted to a lazily-loaded
  syntax-diff.tsx (plain DiffBody fallback — same output Shiki's own
  pre-resolve state showed); codeToTokens becomes a dynamic import that
  runs only once a highlightable diff is on screen.
- @streamdown/code: statically ran createJavaScriptRegexEngine() and built
  full language registries at module scope on every launch. It now loads
  via useCodePlugin() on first markdown mount and swaps into the plugin
  table when it lands; fenced code renders plain until then, identical to
  the delay fallback users already see during streaming.

Interleaved A/B vs main (prod build, warm V8 cache, median of 4, two
alternating rounds): FCP 1100->940ms, DOMContentLoaded 618->542ms,
nav-to-interactive 1312->1123ms on the quiet round; same direction with
larger gaps under load. ~160-190ms off every renderer boot metric.
2026-07-27 20:44:35 -05:00
Brooklyn Nicholson
6fb5d2d89c perf(desktop): split heavy lazy-only libs out of the renderer entry chunk
codeSplitting:false inlined every lazy()/dynamic import into the entry, so
the whole 28.5 MB bundle — including 19 MB of shiki grammars and 3.1 MB of
mermaid that nothing rendered — was parsed and evaluated on every cold
start. The original single-chunk rationale (#38888: electron-builder OOMs
scanning shiki's thousands of default chunks) doesn't require ONE chunk,
just few: rolldown advancedChunks groups keep the file count at ~180.

Ordering matters and is the subtle part: shared foundations (react, hast/
mdast utils, lodash-es/d3 commons) must match BEFORE the heavy groups,
because rolldown merges an unmatched shared module INTO the heavy chunk
that uses it — and then the entry statically imports 19 MB of shiki just
to reach react, putting the whole chunk back on the boot path.

Statically-reachable boot graph: 26.9 MB -> 7.7 MB.
2026-07-27 20:44:35 -05:00
Alex Fournier
347252a296 Merge upstream main into feat/hermes-relay-shared-metrics 2026-07-27 18:29:17 -07:00
hermes-seaeye[bot]
71e7eb3c16
fmt(js): npm run fix on merge (#73023)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 01:20:15 +00:00
brooklyn!
fe71e32a87
Merge pull request #72893 from NousResearch/bb/desktop-activity-grouping
Collapse a turn's tool activity into one grouped, live-ticking line
2026-07-27 20:11:28 -05:00
brooklyn!
094a558635
Merge pull request #73015 from NousResearch/bb/layout-aware-keybinds
fix(desktop): resolve keybinds through the active keyboard layout
2026-07-27 20:05:51 -05:00
Brooklyn Nicholson
83cc5831fd fix(desktop): resolve punctuation keybinds through the active layout
Letter chords now follow `event.key`, but punctuation was still anchored
to the physical QWERTY position, so the shipped punctuation defaults stayed
unreachable on a remapped layout. On Dvorak `mod+.` (command center) reads
the physical V key, and `mod+,` / `mod+/` land on `w` / `[`.

Take `event.key` for unshifted punctuation too. Shift stays excluded because
a shifted `event.key` is the shifted glyph ("?" for "/") and combos are
anchored to the unshifted token. Digits stay physical: AZERTY types "&" on
the unshifted "1" key, so `event.code` is what keeps `mod+1` bound. Glyphs
we do not ship as tokens — Option output, dead keys, non-Latin scripts —
fail both checks and fall back to the physical code.

The punctuation set derives from CODE_TO_KEY so the two cannot drift.
2026-07-27 19:57:51 -05:00
LeonSGP43
b2bfab4837 fix(desktop): honor layout-aware letter keybinds 2026-07-27 19:54:55 -05:00
Brooklyn Nicholson
bc8933042f fix(desktop): stop the enter animation pinning opacity over the stylesheet
Two identical tool rows, one above the other, rendered at two different
opacities — and no amount of hovering would even them out.

The enter animation fills forwards, so its final keyframe is held in the
animation origin of the cascade for as long as the element lives, above the
author stylesheet. Naming `opacity: 1` there didn't just end the fade, it
permanently overruled the resting opacity of every element the sheet dims.
Transcript scaffolding is dimmed exactly that way, so a row kept whichever
opacity it happened to mount with: full if it animated in during the turn,
faded if it was rehydrated or remounted past its one-shot key. Same for
thinking headers, which is why "Thought" never matched the rows near it.

Leave the end opacity out of the keyframe. It animates up to whatever CSS
asks for and keeps answering to it, hover included.

Then close the way the surfaces drifted in the first place: the fade named
each one in its own selector, so the live status line — added later, and
neither tool nor thinking nor prose — matched none of them and sat a shade
brighter than the rows either side. One `data-conversation-scaffold` mark
now carries it, and every surface opts in.
2026-07-27 19:54:47 -05:00
Alex Fournier
147e451cc8 Merge upstream main into feat/hermes-relay-shared-metrics 2026-07-27 17:54:42 -07:00
Jaaneek
d83e858507 chore(xai): drop noisy default_headers comment 2026-07-27 17:47:28 -07:00
Jaaneek
5cffc53194 fix(xai): send Hermes-Agent User-Agent on chat/completions
Direct tool HTTP calls already identified as Hermes-Agent, but the main
OpenAI-SDK chat path still sent OpenAI/Python. Set Hermes-Agent/<ver> for
api.x.ai clients (xai + xai-oauth) so normal text traffic is attributed correctly.
2026-07-27 17:47:28 -07:00
hermes-seaeye[bot]
d89512107e
fmt(js): npm run fix on merge (#73004)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 00:46:46 +00:00
brooklyn!
e56480057c
Merge pull request #73003 from NousResearch/bb/tooltip-skip-delay
fix(desktop): zero Tip skipDelayDuration so the hover delay actually sticks
2026-07-27 19:38:51 -05:00
Brooklyn Nicholson
7a10e48e2f fix(desktop): one weight and one gap for transcript scaffolding
The scaffold rows shared a colour but not a weight: tool summaries and the
ticker rendered medium against the reply's normal-weight prose, which read
as emphasis on the quietest lines in the column.

Spacing had the matching problem. The block-gap rule listed which *pairs* of
blocks qualified, so the live status line — neither tool, thinking, nor
prose — fell through every branch onto its own half-size margin. And because
a streaming turn is sealed into several bubbles as it goes but rehydrates
into fewer, two blocks are siblings inside one bubble or split across two
depending on when you look; the flex gap between bubbles is half the block
gap, so the rhythm tightened and relaxed as a turn settled. Cover every
top-level block with one rule, keep prose-to-prose on paragraph rhythm, and
top the between-bubble gap up to match.
2026-07-27 19:35:04 -05:00
Brooklyn Nicholson
46df6ca705 fix(desktop): keep a run live between sequential calls
A run counted as live only while one of its calls was unresolved, which is
false for the instant between one sequential call finishing and the next
arriving — and for a string of commands that instant is most of the run. It
settled and re-opened between every call, so the ticker unmounted and came
back at the top of its reel instead of scrolling, and the summary flipped to
past tense while work was still going.

Live is now "the turn is working and nothing follows this run", with the
tail bound still settling a run the agent has moved past. The summary takes
its present-tense clause from the most recent call when none is pending —
the same call the ticker is showing. The stall spinner stays out of the way
while a run narrates, rather than stacking a second timer under it.
2026-07-27 19:35:04 -05:00
Brooklyn Nicholson
0b0e53cee1 fix(desktop): retire the drafting label when the model moves on
`tool.generating` names the tool whose arguments are streaming, and nothing
ever closed that claim: there is no stop-drafting event, and a draft can be
abandoned without reaching `tool.start` when a mid-stream retry drops a
partial call or a guardrail blocks the tool. Enumerating the ways a draft
ends left those holes open, so "Editing" sat under the transcript for the
rest of a multi-iteration turn.

Invert the rule — the claim only covers what the model is emitting right
now, so any other output from that session retires it. Stopping the turn
clears it too, and a `tool.generating` that arrives after the stop is
ignored on the same condition `mutateStream` already drops late tool rows.
2026-07-27 19:34:54 -05:00
Brooklyn Nicholson
ca605174cf fix(desktop): zero Tip skipDelayDuration so every tip waits its delay 2026-07-27 19:31:55 -05:00
Alex Fournier
ba4f5b893a Merge upstream main into feat/hermes-relay-shared-metrics 2026-07-27 17:28:56 -07:00
Teknium
373632e338 fix(state): recover from read-only database files at startup
Port from Kilo-Org/kilocode#12508: a stray read-only state.db / -wal /
-shm (sudo run, restored backup, copied dotfiles) previously killed
SessionDB init with an opaque 'sqlite3.OperationalError: attempt to
write a readonly database' raised from deep inside _init_schema —
naming no file and no fix — and the obvious wrong 'fix' (deleting the
-wal) silently loses committed transactions.

New preflight_db_writability() runs before the first connection on both
DB open paths (SessionDB.__init__ and hermes_cli.kanban_db.connect):

- files inside the Hermes home tree are repaired with chmod u+rw (the
  safe scope: Hermes owns them, and the OS makes chmod fail on files
  the user doesn't own, which bounds the repair exactly);
- anything else (root-owned files, read-only mounts, custom paths)
  fails fast with an error naming the exact file and the exact chmod
  command, plus an explicit 'do NOT delete the -wal' warning;
- WAL sidecars are never deleted or truncated — once writable, the
  normal open path checkpoints committed frames into the DB.

Proven live on main first: chmod 444 state.db -> SessionDB() raises the
opaque readonly error. With the fix: in-home DBs self-heal; out-of-home
DBs get the actionable message. Sabotage run confirms the integration
tests fail without the wiring (2 failed / 10 passed).
2026-07-27 17:27:38 -07:00
brooklyn!
f800aa3cae
Merge pull request #72995 from NousResearch/bb/edit-composer-submit
fix(desktop): send a message edit when the arrow is clicked
2026-07-27 19:27:34 -05:00