Commit Graph

2157 Commits

Author SHA1 Message Date
Teknium 94be919411 feat: rename Codex OAuth provider label to "ChatGPT or Codex Subscription"
Renames the openai-codex provider's display label across the CLI
(hermes model picker, provider labels), the dashboard OAuth accounts
catalog, and the Desktop onboarding + settings provider pickers.
Slug, aliases, and auth flows are unchanged.
2026-08-13 03:06:08 -07:00
Teknium ae56c97c60 feat(desktop): show full session title in a tooltip when it truncates
Hovering a sidebar session row (one-line or card) now shows the complete
title in a styled tooltip — but only when the title actually overflows
its label, so fully visible titles never grow a redundant tip.

New OverflowTip primitive in ui/tooltip.tsx: a controlled Tip that
measures scrollWidth vs clientWidth on pointerenter and arms a 600ms
deliberate-hover delay only when the content is truncated. Community
request via @fhreire on X.
2026-08-13 02:38:32 -07:00
hermes-seaeye[bot] cc389f8155 fmt(js): npm run fix on merge (#85175)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-13 09:21:50 +00:00
Teknium 926c8d591a feat(desktop): cron + blueprint recipes in the sidebar nav rail
Adds a 'Scheduled jobs' row to the sidebar's top nav (below Artifacts,
watch codicon, wired to the existing nav.cron keybind action) so the
cron overlay opens from the same rail as New session / Capabilities /
Messaging / Artifacts.

Inside the cron overlay, the list rail now also shows the Automation
Blueprint recipes below the jobs (same search box filters both).
Clicking a recipe opens the create dialog pre-seeded to that
blueprint's typed-slot form via a new optional blueprintKey on the
create EditorState. Catalog fetch reuses the ['cron-blueprints'] query
key, so no extra request.

i18n: sidebar.nav.cron added to en/zh/zh-hant/ja (ar already had it).
2026-08-13 02:12:23 -07:00
Teknium cfc5e098f2 fix(sdk): keep all-profiles sidebar scope on cross-profile openSession (#85155)
ensureGatewayProfile narrows the Sessions sidebar to the activated
profile as a side effect, so every cross-profile open from a plugin
surface silently locked the user into that profile's session list. A
plugin-driven open is a navigation, not a scope choice: openSession now
restores the unified all-profiles view after a cross-profile activation
(keepAllProfilesScope, default true; pass false for the old narrowing).
Same-profile opens write no scope at all.
2026-08-13 01:50:04 -07:00
hermes-seaeye[bot] 90c7180dce fmt(js): npm run fix on merge (#85140)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-13 08:15:48 +00:00
kshitij a4f468e832 refactor(gateway/desktop): consent-first truncation precedence + dedup (simplify pass)
Final-diff simplify/review pass findings on #83785:

- Consent gate (confirm_truncate -> 4029) now checked BEFORE target
  resolution, restoring the pre-PR precedence: an unconfirmed submit
  carrying truncation params refuses without paying the durable-transcript
  read or heal-stamping live history dicts, and an unconfirmed out-of-range
  ordinal returns 4029 (not 4018). Malformed params still refuse first
  with 4004. Regression test added (spy DB asserts zero reads pre-consent;
  mutation-checked against the previous commit).
- _coerce_truncate_ordinal generalized to _coerce_truncate_int(param_name):
  the row_id branch was inlining the exact bool-guard + int() -> 4004
  pattern the helper had just extracted.
- Deleted the dead user_indices re-read after _resolve_truncate_row_id
  (heal mutates dicts in place; the filter output is identical) and the
  duplicate range check that had deadened the pre-existing guard.
- Desktop: exported isVisibleUserMessage from use-prompt-actions/utils and
  used it in visibleUserOrdinal / visibleUserIndexAtOrdinal /
  rebindSurvivorRowIds — one predicate for the ordinal parity all three
  depend on instead of three verbatim copies.
- Docs: programmatic-integration.md documents survivor_user_row_ids.
2026-08-13 13:35:55 +05:30
kshitij f6081b6647 style(desktop): satisfy perfectionist import order + padding lint rules
check:lint failed on the two sort-named-imports errors (survivorRowIdsFrom
before type SurvivorUserRowIds) introduced by the rebind commit; also adds
the blank line eslint wanted in the new test helpers.
2026-08-13 13:35:55 +05:30
kshitij 42eec4ab38 fix: return survivor row ids after rewind so clients can rebind stale rowIds
Review follow-up (StanleyStetson + egilewski on #83785/#83202): a successful
rewind's replace_messages(archive_dropped=True) re-inserts the surviving
prefix as NEW SQLite rows. Gateway memory picks up the fresh _row_id stamps
via lastrowid, but the Desktop's surviving bubbles kept their pre-rewind
ChatMessage.rowId — so a second rewind/edit/regenerate of an older surviving
turn sent a stale truncate_before_row_id and was (correctly) refused with
4018 until a transcript reload. Fail-closed stays untouched, per both
reviews; the fix is rebinding, not ordinal fallback.

Server: prompt.submit now returns survivor_user_row_ids (fresh post-rewrite
ids of surviving visible user turns, in visible-user-ordinal order) on both
the inline and compute-host paths whenever a durable truncation committed.

Desktop: runRewindSubmit surfaces the field; restore/edit/reload on both the
primary chat and session tiles rebind surviving user bubbles positionally
(same visible-user filter the ordinal math uses) and clear any rowId they
cannot rebind — a cleared id degrades to the ordinal path instead of a 4018.
Absent field (older gateway) leaves state untouched.

Tests: consecutive-rewind regression on a real SessionDB (stale id 4018s,
returned id succeeds; mutation-checked) + vitest for survivorRowIdsFrom /
rebindSurvivorRowIds (rebind, null-clear, past-end clear, hidden skip,
identity preservation).
2026-08-13 13:35:55 +05:30
kshitij 4aeb6f4a4f style(desktop): drop stray semicolons in rewind.ts
Three PR-introduced trailing semicolons in a semicolon-free file;
rewind.test.ts re-run green (5/5) with the change in place.
2026-08-13 13:35:55 +05:30
StanleyStetson 23da6d6fe2 fix(gateway/desktop): durable row-id addressing for rewind truncation
Address rewinds/edits via SQLite messages.id (truncate_before_row_id)
instead of shifting user ordinals. Resolve against in-memory stamps,
then durable session history when live turns drop _row_id; refuse
unknown durable targets with 4018 (no ordinal fallback) and 4030 on
ordinal/row_id mismatch. Stamp _row_id on insert, load row ids on
resume paths, send rowId from Desktop, filter renderer-synthetic ids,
and stop silently resending failed targeted edits without truncation.
Add production-shaped SessionDB tests for resolve and fail-closed paths.

Fixes #82959
2026-08-13 13:35:55 +05:30
Brooklyn Nicholson 9deb0302ca fix(desktop): the sidebar remembers grouping per workspace scope
The Project-grouping flag was one global bool while the grouping beneath it
was already stored per scope (workspace vs all-profiles). Picking Project
inside a workspace therefore dragged the all-profiles view into the project
tree and vice versa — "I have to re-set grouping every time I switch."

The flag now lives per scope like its sibling grouping atoms (the flat key
keeps its historical name so existing choices survive), setSidebarGrouping
writes to the scope it just switched INTO when Profile flips the view, and
reset clears both scopes.
2026-08-13 02:18:53 -05:00
Brooklyn Nicholson 9be9925467 fix(desktop): themed fade scrollbar on the virtual list, not overlay
scrollbar-overlay opts out of the themed thin scrollbar; on Windows there
are no native overlay scrollbars, so Chromium painted the classic
always-visible gutter instead — a permanent scrollbar next to the recents
list. The themed fade bar reserves its 4px on every platform but stays
invisible until hover, and the wrapper no longer stacks a second scroller,
which is what the overlay class was originally working around.
2026-08-13 02:18:53 -05:00
Brooklyn Nicholson 81587c4f8f fix(desktop): inbox cards render in every sidebar view, not just flat recents
The card prop was gated off whenever Project grouping was active, so the
Inbox style toggle silently did nothing there. It is a render variant, not
a grouping: project lanes and overview previews now render the same card
the flat list does.

Also mirrors the section's real virtualization inputs (projectOverview /
entered-project content, not the persistent agentProjectTree cache) when
deciding the wrapper's scroll classes, and stops gating SCROLL_Y on that
parallel guess — the section is the single authority on which scroller
lives, so the recents pane can no longer end up with no scroller at all
(the "no sessions under Updated grouping after toggling settings" blank).
2026-08-13 02:18:53 -05:00
Brooklyn Nicholson e7032bb267 feat(desktop): recurrence-to-cron suggestion provider
Third draft provider: recurring phrasing in the draft ("every morning",
"daily", "each week") offers a Schedule-this pill. Click prefixes the
draft with an explicit scheduling instruction and the agent creates the
job via its cronjob tool on send — the pill never schedules anything
itself. Proper-noun guard keeps titles like "the Daily Prophet" quiet;
hyphen-as-word-char keeps "weekly-report.pdf" quiet.
2026-08-13 02:17:42 -05:00
Brooklyn Nicholson c8cad1cc9e feat(desktop): skill-match and connection-repair suggestion providers
Two new sources on the suggestion bus, one per provider shape:

- skill (draft): the draft names an enabled skill (whole-word,
  4+ chars), so offer to lead the message with its /command. Invoke
  prefixes the draft via the new 'prefix' insert mode and stands down
  once the draft starts with a slash; skill_manage invalidates the
  cached index alongside the slash-completion cache.
- repair (event): an mcp__ tool call failing with auth/connection-
  shaped output offers a one-click reconnect for that server, fed from
  the gateway tool.complete handler. Reconnect runs the shared OAuth
  flow with server-side cancel and reloads live tools before claiming
  success; a later successful call to the same server withdraws the
  offer on its own.
2026-08-13 02:17:42 -05:00
Brooklyn Nicholson a3da6d8071 feat(desktop): quiet suggestions the user has repeatedly ignored
The bus now keeps a session-scoped declined ledger: a pill the user
watched appear and let die three times stops re-offering for the rest
of the session. Acting on a pill clears its count, so a suggestion
that was taken can come back for the next trigger. In-memory on
purpose — a fresh session is a fresh chance.
2026-08-13 02:17:42 -05:00
Carl Taylor 654435210c feat(cron): surface model drift impact in Desktop 2026-08-12 23:47:22 -07:00
hermes-seaeye[bot] 1ef0a366da fmt(js): npm run fix on merge (#85098)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-13 06:44:04 +00:00
Teknium 89a84e1ae6 feat: profiles.list/profiles.create ws RPC + plugin session-navigation doors (#85093)
Desktop plugins reach the backend exclusively through the generic ws
JSON-RPC door (host.request), but profile enumeration/creation only
existed on the dashboard REST router, which plugins cannot reach — so
anything 'one chat per agent profile'-shaped (bot rosters, profile
pickers, team panes) was impossible to build as a plugin.

- tui_gateway/methods_profiles.py: new @method handlers
  * profiles.list — profiles + optional last_session preview per profile
    (mirrors session.list's kanban/tool deny-list; best-effort per-profile
    state.db probe degrades to null instead of failing the call)
  * profiles.create — ws twin of POST /api/profiles (clone_from/clone_all/
    no_skills/description), plus optional SOUL.md content and a best-effort
    model+provider pin; mirrors the CLI flow (seed skills, safe alias)
  Both run on the RPC pool, not the WS reader thread (list_profiles walks
  skill trees; create copies bundles).
- SDK: host.openSession(id, { profile, intent }) — open a stored session
  the way core surfaces do, soft-swapping to the owning profile's backend
  first (ensureGatewayProfile), and host.newChat(profile) — fresh draft in
  a named profile (same door as the sidebar's per-profile '+').
- Docs: desktop-plugin-sdk.md gains both surfaces.

First consumer: a Grok Bot-style 'Bots' roster plugin (one persistent
chat per agent profile with a New Agent dialog) built on exactly these
four doors.
2026-08-12 23:33:58 -07:00
Brooklyn Nicholson 91a30705eb refactor(desktop): generalize the composer suggestion pills into a provider bus
The pill strip from the inline-MCP work is worth more than one source, so
the MCP-specific store splits into two layers with the same UX contract
(session-scoped, capped, self-limiting, one-click with narrated
idle→working→done):

- store/composer-suggestions.ts — the bus. Draft providers register into
  the existing debounced sampler; event providers push/withdraw directly.
  Offerings merge (event before draft), dedupe by provider-namespaced key,
  and keep reference identity on no-ops.
- store/suggestion-providers/mcp.ts — the founding provider, behavior
  unchanged: directory keyword/host matching, configured-server exclusion,
  one-click connect with OAuth cancel + config rollback.
- composer/suggestion-pills.tsx — the generic strip; phases and cancel
  live here, action/rollback/toasts stay with the provider's invoke.

No new pills yet — this is the seam for them.
2026-08-13 01:22:35 -05:00
Brooklyn Nicholson fd6af8f832 feat(desktop): render the clarify (Recommended) label in tertiary text
The card reads the labelled choices off the gateway request rather than
the raw tool args -- the backend applies the label there, and the card
only mounts once the request exists, so the args are a hydration-race
fallback. RECOMMENDED_LABEL and bareChoice live in the clarify store so
the component and the choice-length guard share one definition; without
the guard a long option could be dropped for length the label added.
2026-08-13 01:19:42 -05:00
Brooklyn Nicholson 6ef0fc4f62 fix(desktop): cancel MCP OAuth flows server-side so a retry doesn't 409 2026-08-13 01:06:51 -05:00
Brooklyn Nicholson 3efce9b98c feat(desktop): suggest MCP servers from the composer draft as brand pills
A renderer-local directory of official hosted MCP remotes (URL-only,
vendor-documented endpoints — deliberately not the reviewed install
catalog) powers keyword and pasted-link suggestions: typing jira or
pasting a *.atlassian.net URL floats an 'Add Atlassian' pill in the
composer's micro-action strip. Matching is whole-word/phrase (unicode
boundaries) plus strict host-suffix on links, host hits outrank
keywords, capped at two, debounced 600ms, and excludes servers already
in mcp_servers. Pills are session-scoped like the micro-action badges
and self-limiting rather than dismissible — they exist only while a
trigger is in the draft. A click drafts the setup request; the agent's
setup_mcp card carries the consent. Brand glyphs extracted from the
mcp-tab into lib/mcp-brands (shared, monochrome marks follow the theme
so GitHub/Notion/Vercel survive dark mode).
2026-08-13 01:06:51 -05:00
Brooklyn Nicholson 6cd4793081 feat(desktop): render setup_mcp as an interactive consent card in the transcript
The card follows the approval bar's consent vocabulary (primary-tinted
action + ghost decline, ⌘⏎/Esc with clarify's focus-stand-down rule) on
clarify's widget shell. Install prefers the reviewed catalog entry (env
prompts inline, background installs polled to completion) and falls back
to the desktop suggestion directory via the validated add-server POST +
OAuth; success reloads live MCP tools before unblocking the agent so it
resumes with the tools it was just promised. Esc stays live mid-flight
as cancel — the abandoned flow aborts at its next poll and a post-write
cancel rolls the config entry back. Typing while the card is pending
declines it and sends normally (skipClarifyRequest's pattern), and the
request/tool.start rows merge on the server arg so reconnects can't
double-render the card.
2026-08-13 01:06:51 -05:00
Brooklyn Nicholson a9eb7e09d9 feat(desktop): marquee clipped inline row titles on hover
The one-line session row gets the exact treatment the inbox card's title
already has: hovering a truncated title glides the clipped tail into view —
one direction at constant speed, a short hold at each end, then a snap back.
Same armMarquee/disarmMarquee handlers, same CSS, so overflow is measured on
pointerenter, short titles never move, hover state lives in DOM attributes
(no re-render of the memoized row), and reduced motion disables it.
2026-08-13 00:55:33 -05:00
brooklyn! 7626105380 Merge pull request #84943 from NousResearch/bb/review-summary-chrome
Self-improvement review row wears the same gold→purple chrome as a memory write
2026-08-13 00:18:44 -05:00
Brooklyn Nicholson 5af2c2ff52 fix(desktop): messages typed during approval/sudo/secret prompts run as the next turn
Typing while the turn was parked on a blocking prompt routed the text
through steer (session.redirect), which sat undelivered behind the
blocked tool batch — nothing rendered, and stopping the turn to force it
through resolved the prompt to empty and ended the turn as the literal
"Operation interrupted." row, eating the message.

Clarify already had a carve-out (typing skips the question and steers)
because a real message IS an answer to a clarify. Approval/sudo/secret
have no such answer path, so the busy submit now queues the words as the
next turn instead: the prompt stays answerable, the queue drains on
settle, and the busy button advertises queue rather than steer while one
is pending. Slash commands still execute inline, and another session's
prompt never affects this one.
2026-08-13 00:13:36 -05:00
hermes-seaeye[bot] 08606fc231 fmt(js): npm run fix on merge (#85024)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-13 04:08:31 +00:00
Brooklyn Nicholson 4d9202b963 feat(desktop): right-click the shell chrome for window-level actions
Right-clicking anywhere the app owns no menu of its own — the titlebar
gutter, an empty pane body, the sidebar background — did nothing at all.
Electron's native handler bails on non-editable, non-selected content by
design, so those surfaces had no menu to fall back to.

Wrap the shell in a fallback context menu carrying the verbs that belong
to the window rather than to a row: new session, new window, command
palette, toggle the status bar, settings, update Hermes. Every row reuses
the store action and the copy its Cmd+K twin already uses, so the two
can't drift.

A guard on an inner element keeps it a fallback: a right-click that lands
inside a surface with its own context menu, on an editable, or on a live
selection stops propagating before Radix's trigger sees it, leaving that
surface's menu — or Electron's native edit menu — in charge.
2026-08-12 22:55:37 -05:00
Brooklyn Nicholson 071d27d1c3 feat(desktop): paste a PR review comment as structured composer context
A pasted GitHub PR comment deep link (#discussion_r… / #issuecomment-…)
now lands as a typed review attachment instead of a bare url chip. The
card attaches optimistically and resolves through gh in the background —
author, file:line anchor, body, and the diff hunk — expanding at send
into an anchored fenced block, so "address this" carries exactly what
"this" is. When gh can't answer (offline, unauthenticated, foreign repo,
remote gateway) the card downgrades to the plain url ref and nothing is
lost.
2026-08-12 22:48:00 -05:00
Brooklyn Nicholson 2960bf37ab feat(desktop): steer a queued prompt into the live turn
Queued turns could only wait for the settle or interrupt the turn to jump
the line. Text-only queue entries now carry a steer action while the agent
is busy: the entry rides the existing mid-turn redirect (no interrupt, no
drain lock), is consumed only when the gateway accepts it, and lifts a
park so the rest of the queue keeps flowing. Slash commands and entries
with attachments keep their existing semantics.
2026-08-12 22:48:00 -05:00
hermes-seaeye[bot] 07ed2fdb60 fmt(js): npm run fix on merge (#85010)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-13 03:33:45 +00:00
Brooklyn Nicholson 8018f9e016 test(desktop): shipped sidebar defaults now include the preview line
The inbox card's preview ships on, so the default row-meta contract is
['preview', 'updated'] — the reset test pins the new shipped view.
2026-08-12 22:24:37 -05:00
Brooklyn Nicholson 8403595f78 style(desktop): satisfy jsx-prop sort on the recents section card prop 2026-08-12 22:24:37 -05:00
Brooklyn Nicholson 92620e4ad3 feat(desktop): live task progress on inbox cards
An inbox card whose session is working through a todo list shows its "X/Y"
fraction at the far right of the footer line, updating live as items
complete. The count projects the runtime-keyed todo map to stored session
ids through the same lineage-alias fallback the working/attention
projections use, emits pre-rendered strings so unchanged rows never repaint,
and skips cancelled items on both sides of the fraction.
2026-08-12 22:24:37 -05:00
Brooklyn Nicholson c96e0daba5 feat(desktop): marquee clipped card titles on hover
Hovering a truncated inbox-card title glides the clipped tail into view —
one direction at constant speed, a short hold at each end, then a snap back
to the start. Overflow is measured on pointerenter and the animation arms
only when the text actually clips, so short titles never wiggle. State lives
in DOM attributes and CSS variables, so a hover never re-renders the
memoized row, and the blanket reduced-motion override already disables it.
2026-08-12 22:24:37 -05:00
Brooklyn Nicholson 45f663b746 feat(desktop): opt-in inbox-style session cards in the sidebar
A new "Inbox style" toggle in the sidebar filter menu renders the flat
recents list as cards: a workspace header line (project when it resolves,
else the cwd leaf, else Home) with the age at its right edge, the title
grouped with a one-line last-message preview, and a model + size footer.
The preview line ships on by default and has its own Show-menu toggle,
offered only while Inbox style is active — the one-line row has nowhere
to put it.

A render variant, deliberately not a grouping — it composes with whichever
grouping is active and only the flat recents list opts in; pinned, project,
and messaging surfaces keep the one-line row. Spacing hangs off a single
--card-gap variable; the title/preview pair is one grouped cell with its own
tighter internal gap. The age and kebab sit in flow inside the header line
rather than a full-height side column, so title, preview, and footer span
the card's entire width.

The card's project label reads through a selector that resolves the label
string, so tree polls with fresh atom identity repaint only rows whose label
actually changed.
2026-08-12 22:24:37 -05:00
Brooklyn Nicholson b6ed6542a1 fix(desktop): stop double scrollbar gutters shaving the recents list
The virtualized recents list sat inside a wrapper that was itself a themed
scroller, so two 4px classic-scrollbar gutters stacked and every row ended
8px short of the sidebar edge the pinned list reaches. Drop the wrapper's
scroller when the virtual list owns scrolling, use the overlay scrollbar on
the virtual scroller (zero gutter, native fade), and neutralize both axes of
the wrapper overflow — `overflow-y-visible` next to `overflow-x-hidden`
computes to `auto` and still reserved a gutter.
2026-08-12 22:24:37 -05:00
Brooklyn Nicholson fb206fd12b fix(desktop): idle sessions without a project color get a visible dot
The idle dot variant had no background of its own, so a settled session
outside any project rendered a literally invisible dot — the row read as
missing its status indicator next to its neighbours. Fall back to the
faintest filled grey; a project color still wins when there is one.
2026-08-12 22:24:37 -05:00
Brooklyn Nicholson 46ebb1b05d fix(desktop): let status-stack titles use the full row before truncating
Todo, subagent, background, and preview titles in the composer status
stack were capped at an arbitrary max-w-[18rem], ellipsizing long items
way before the row ran out of space. The spans already live in the
shared StatusRow's min-w-0 flex-1 content slot, so plain truncate gives
correct overflow at the actual row edge — drop the cap.
2026-08-12 21:00:40 -05:00
brooklyn! 79ad5bc353 Merge pull request #84955 from NousResearch/bb/paste-to-focus
feat(desktop): paste into the composer without focusing it first
2026-08-12 20:53:13 -05:00
Brooklyn Nicholson 34e4ca14e8 feat(desktop): paste into the composer without focusing it first 2026-08-12 20:45:53 -05:00
Brooklyn Nicholson 985594aaa3 fix(desktop): one git glyph on the composer's coding row, with the PR number leading
The branch's PR chip carried its own pull-request icon next to the row's
branch glyph, so the strip opened with two git marks in a row. The chip now
takes showIcon, and the coding row renders it glyph-less and ahead of the
branch name — the leading branch icon covers both, and the row reads
icon → #number → branch. Sidebar rows keep the full chip.
2026-08-12 20:34:58 -05:00
Brooklyn Nicholson ebf8443604 fix(desktop): paint the self-improvement review row as the memory write it is
The background review's summary fell through SystemMessage's generic branch: centered, 60% width, muted grey — while the memory tool row it reports on wears the gold-to-purple legendary chrome. Type the event with a review: marker at the gateway handler (same convention as steer: / slash:) and give it the brain glyph, gradient label and purple detail, left-aligned in the reading column.
2026-08-12 20:24:30 -05:00
Teknium 4c8e326500 Merge PR #62319 (keyring-less Linux token storage) onto current main 2026-08-12 16:38:35 -07:00
Teknium f525772725 fix(desktop): keep config/structured code blocks fenced instead of unwrapping to prose (#84664)
* fix(desktop): keep config/structured code blocks fenced instead of unwrapping to prose

The desktop markdown preprocessor has a "prose fence" heuristic that
strips the fence off blocks it thinks are wrapped prose. Its
`proseLines >= 3 && codeSignals === 0` rule fires on ANY 3+ line
plaintext block with no JS/SQL tokens -- which is exactly what an SSH
config, a .env dump, or any INI/key-value listing looks like. The result
was that a fenced ```-block of SSH config rendered as a flat paragraph
instead of a code block.

Add isLikelyStructuredText() and use it as a veto in both
isLikelyProseFence() and isLikelyProseCodeBlock(): a block is treated as
structured (and kept fenced) when it has indented continuation lines, or
when it has no sentence-ending punctuation and a majority of lines are
`Key value` / `Key: value` directives. Real wrapped prose has
sentence-shaped lines and no per-line indentation, so it still unwraps as
before. The bullet-prose case in isLikelyProseCodeBlock is checked first
so markdown bullet lists remain prose.

Tests: markdown-code.test.ts gains SSH-config / flat-config / .env
regression cases for both functions, plus direct isLikelyStructuredText
coverage, and re-asserts that genuine paragraph prose still unwraps.

* fix(desktop): tighten config-line detection to not match punctuation-less prose

The first CONFIG_LINE_RE matched any 'word word' line, so a wrapped prose
fragment with no sentence punctuation (e.g. 'the quick brown fox jumps')
was misread as a config directive and its fence kept. Split into an
explicit-separator form (Key: value / Key = value) plus a short 2-3 token
'Key value' directive form; a real sentence line has more tokens, so
punctuation-less prose is no longer treated as config.
2026-08-12 11:08:55 -07:00
Teknium 9d6c5a920c fix(desktop): collapse Fireworks behind the provider disclosure on first run
The first-run provider picker showed Fireworks AI alongside Nous Portal
before the user opened the 'Other providers' disclosure. Only Nous Portal
should be visible up front; Fireworks now lives inside the expanded list
but keeps its #1 position there (Nous -> Fireworks ordering preserved).
2026-08-10 21:09:27 -07:00
Brooklyn Nicholson 1e6a7b3315 fix(desktop): scope custom provider settings to the active profile
The custom-endpoint REST handlers ran bare load_config/save_config, so
every add/activate/delete landed in the process-level default profile
regardless of which profile the desktop settings UI was targeting. A
provider added under a non-default profile silently went to default:
visible only in default-bound sessions, absent everywhere else, and
un-addable to another profile without hand-editing its config.yaml.

Scope all four handlers (list/upsert/activate/delete) to the requested
profile via _config_profile_scope, matching /api/config, and spread the
active profile into the four hermes.ts wrappers alongside their existing
validateCustomEndpoint sibling.
2026-08-10 22:26:54 -05:00
hermes-seaeye[bot] a1bfbccc02 fmt(js): npm run fix on merge (#83539)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-11 00:20:02 +00:00