Commit Graph

3108 Commits

Author SHA1 Message Date
teknium1
85e11aaa00 fix(profiles): carry install records and skip install staging when cloning plugins
The light clone now copies plugins/ through the profile copier: bytecode caches,
sockets and the installer's in-flight .install-*/.update-* staging dirs stay
behind, while .install-metadata.json (revision + catalog provenance) travels, so
a catalog-installed memory provider runs at the source's revision in the clone
instead of resolving nowhere. The CLI names the plugins it carried.

Co-authored-by: Eri Barrett <eri@plasticlabs.ai>
2026-10-02 01:29:41 -07:00
teknium1
9de7853a05 docs(tool-search): describe local tool_call batch splitting
The page still said multi-local and mixed tool_call batches are rejected.
The agent now splits a batch containing local tools into one call per
entry; document that and the connector-only dispatch.
2026-10-01 23:50:31 -07:00
teknium1
a184f85924 docs: explain Desktop's default vs Classic Hermes and the reserved classic name
On Desktop `default` and /skin default mean Nous (a CLI/TUI or config switch
to `default` moves a Desktop pick back to Nous), the gold look is the
separate `classic` theme reached by /skin classic, gold or hermes, and
`classic` joins the Desktop built-in names a user skin file cannot recolor.
2026-10-01 22:27:00 -07:00
teknium1
a3149156ec feat(cli): make the summary the default hermes status, --full for every section
`hermes status` now prints the one-screen summary (model, provider, connected
providers, gateway, platforms, jobs); `--full` (alias `--all`, which never
changed the output) and `--deep` print every section as before. Hermes
Console keeps the full report since it accepts no flags.

The summary reads the canonical sources instead of re-deriving them:
- Providers: list_authenticated_providers, the list the /model picker offers
  (cached catalogs only, no custom-endpoint probes), so it honours
  model_catalog.excluded_providers, pools and plugin providers for free.
- Platforms: GatewayConfig.get_connected_platforms, the gateway's own
  decision about which adapters to start (it also rejects placeholder tokens).

The full Messaging Platforms section now uses the same verdict for plugin
rows. It called check_fn, a passive "SDK importable" probe that is True for
every bundled plugin, so a fresh install listed Discord, Slack, Matrix,
Teams, WhatsApp and ~9 more as "configured" with no credentials.
2026-10-01 22:03:11 -07:00
teknium1
fd6a6fbf2e docs: relative links in the catalog submission pages (docs-site link check) 2026-10-01 21:58:11 -07:00
teknium1
d802c47238 docs: one complete plugin catalog submission guide, synced with the README rules
Submitters only saw a stale 5-point summary on the catalog page (it even listed
a 'Released' requirement nothing enforces), while reviewers applied ten README
rules plus criteria written nowhere public (credentials, approvals, disclosure,
compatibility metadata, forks of bundled plugins).

- plugin-catalog/README.md: rules 11-15 write down those review criteria; the
  rule list sits between admission-rules markers.
- New developer-guide page 'Submitting to the plugin catalog': before you
  submit, the PR flow, the full rule block (verbatim mirror), pin updates,
  delisting vs removal, asking for a hook.
- The /plugins 'Submit' links and the user-guide section point at it.
- tests/website/test_catalog_rules_mirror.py fails when the two rule blocks
  differ; classify_changes makes a README-only edit run it.
- AGENTS.md, plugins/AGENTS.md and CONTRIBUTING.md reference both files.
2026-10-01 21:58:11 -07:00
teknium1
a959a6ede1 fix(wecom): split long messages instead of silently truncating them
Both WeCom adapters dropped everything past the platform limit with no
error: the AI Bot adapter sliced markdown content at 4000 chars, and the
callback (self-built app) adapter sliced text at 2048 *characters* while
message/send keeps only the first 2048 *bytes* (so CJK replies lost
everything past about 680 characters). Cron output was additionally cut
to 4000 chars with a gateway-host file pointer.

AI Bot: send() queues one markdown message per MAX_MESSAGE_LENGTH chunk,
so every chunk draws its own token from the 30 msgs/min per-chat bucket
(errcode 846607), and each goes out on the cached reply req_id like any
other send in that chat (group chats cannot send proactively). Callback: chunk by UTF-8 bytes at 2048.
Both declare splits_long_messages so cron delivery hands over the full
payload. Each stops at the first failed chunk and returns its error.

Fixes #19689
Fixes #107443

Co-authored-by: luyao618 <364939526@qq.com>
Co-authored-by: briandevans <252620095+briandevans@users.noreply.github.com>
Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
Co-authored-by: Andy <sz.private@gmail.com>
2026-10-01 17:18:56 -07:00
dhruv.kejriwal
164a697d0c fix(cron): ignore measured durations in the incident error signature
The inactivity watchdog polls every 5s, so the same stall reports
"idle for 601s" on one run and "idle for 604s" on the next. The incident
signature hashed that raw number, so each run minted a new incident: an
acked timeout pinged again and the repeat-alert cooldown never applied.
Mask duration tokens before hashing; status codes and other numbers still
split incidents.

Fixes #129587
2026-10-01 20:14:31 -04:00
FroRaut
4152c69636 fix(whatsapp): bind cloud webhooks to configured identity
(cherry picked from commit a2efc1d62c5be444274ae58fda1aa86d1d9793ee)
2026-10-02 02:58:18 +05:30
kshitijk4poor
ed7909bad9 refactor(webhook): drop dead revision snapshot, lock-free reads, CAS owns the disable rule
The sha256 revision returned by _load_subscriptions_unlocked was never compared by
anyone, so _load_subscriptions_snapshot was dead weight; the loader now returns the
dict and folds its duplicate excepts. _load_subscriptions reads without the store
lock: writers publish by atomic rename, so readers always see one whole snapshot,
and read-only homes (where the lock file cannot be created) keep listing routes.
Nothing re-enters the lock any more, so the thread lock is a plain Lock.

The "keep an explicit enabled: False" rule was copied into both the CLI subscribe
path and the dashboard create path; it now lives in _replace_subscription after the
CAS check (where the current record is authoritative) and the published route is
returned, so the dashboard summary reflects what was actually stored.

The rebind secret selection collapses to one expression (same result for all four
profile-changed x --secret combinations), and the test-only _save_subscriptions is
removed; tests seed through _mutate_subscriptions.

The gateway's dynamic-route reload now records the stat key for a malformed or
non-dict file too, so a bad version is parsed once rather than on every POST while
the last good routes stay live. Docs describe the reload as stat-gated and lock-free.
2026-10-02 02:54:18 +05:30
FroRaut
3082d00c82 fix(webhook): transact subscription mutations
(cherry picked from commit 8c4ee86733a1193687f379e158e870ee4bcf56b5)
2026-10-02 02:54:18 +05:30
teknium1
a743ca54c3 Revert "docs: approvals.timeout and agent.clarify_timeout are the messaging-platform deadlines"
This reverts commit d7fdebf45a.
2026-10-01 13:23:12 -07:00
teknium1
423eef696e Revert "chore: review follow-ups — stale timeout comments, softer docs claim, group-turn cap credits only user-blocked intervals"
This reverts commit bdf4a5d632.
2026-10-01 13:23:12 -07:00
John Paul Soliva
00e781e193 docs(worktrees): hermes -w checks out without the repository's hooks, fsmonitor and filters
Note the Git LFS consequence (pointer files) and the fix (git lfs pull in the worktree), EN and zh-Hans.

(cherry picked from commit 9870e29a42a77865eac79d01d3b99d516e5ebf0f)
2026-10-02 01:24:41 +05:30
teknium1
aa859fd4c8 feat(plugin-catalog): refuse plugins that override Hermes core or Desktop at runtime
Catalog policy (Teknium): a listed plugin extends Hermes only through public
surfaces. A plugin that replaces core functions, methods or Desktop UI in place
collides with every other plugin patching the same seam and breaks on any core
release.

- New `no core override` admission check (hermes_cli/plugin_validate_core_override.py):
  static taint pass over the import closure of __init__.py and dashboard/*.py.
  Core values are Hermes modules (plus hermes_plugins.*) and attribute chains off
  them, reached by import, importlib/sys.modules lookups, getattr, iteration, or a
  plugin helper that returns one. Rebinds are attribute assignment/deletion,
  setattr/delattr, sys.modules writes, writes into a core module's tables,
  mock.patch, and passing a core value to a helper that setattr's its parameter.
  Tests, benchmarks and CI/smoke scripts outside the import closure are not judged.
- Desktop lint: new `app DOM reach` rule (document-level queries of the app's own
  data-slot/data-tour/data-sidebar/data-testid markup, and document.body
  childList/subtree MutationObservers).
- Desktop lint bug: the comment stripper did not skip string literals, so a string
  holding `/*` (a glob like '**/*.md') opened a fake comment and hid every line up
  to the next `*/` from all rules. Strings are now matched first and kept.
- README rule 9, the plugin-catalog user guide and the plugin developer guide's
  compatibility contract state the rule.

A/B over 466 pinned trees (121 open catalog PR entries + 349 listed entries +
humalike): the new checks flag exactly the hand-verified overrides; zero crashes;
no previously reported Desktop finding disappears.
2026-10-01 12:41:57 -07:00
kshitijk4poor
5a8dea85fb docs(webhook): header-less deliveries get a random per-request id, not a timestamp 2026-10-02 01:07:31 +05:30
kshitijk4poor
9ab49e73b4 fix(web): honest guest refusal, and one route walk per search dispatch
Two review findings on the free fast-search route.

A guest with a stored `web.backend: nous` holds a real Nous identity and a
usable token; the route excludes it by tier, not by absence. The refusal said
"no Nous identity or unreachable", which is wrong for exactly the cohort this
route was gated against. Reuse the canonical FREE_TIER_NEEDS_ACCOUNT text for
the anonymous tier and keep the unreachable wording for everyone else.

search() then called _managed_web_search() a third time just to choose that
message, after _managed_gateway() had already resolved the route — a full
autodetect ladder plus a Portal account read and token peek. Let _managed_gateway
accept the answer its caller already computed so a dispatch walks the ladder
once.

Docs: the autodetect row implies the keyless switch governs a registered
identity's web_search. It does not.
2026-10-01 23:55:54 +05:30
kshitijk4poor
cee3000c24 fix(web): exclude the anonymous guest tier from free fast search
The managed Perplexity ``search_type: "fast"`` route is served without funding
checks, but eligibility is still identity-scoped: the anonymous guest tier has no
Portal account behind it, so it must keep the keyless ring rather than claim a
route the gateway may not serve it.

``resolve_free_search_gateway`` resolved on the presence of a Nous token alone,
and the guest tier has a perfectly usable one, so every anonymous install would
have been routed to the gateway. Add ``fast_search_entitled`` — a registered
Portal identity of any tier, no credit or tool-pool requirement, anonymous tier
excluded, failing closed on unknown/error — and gate the free route on it.

Guests are byte-identical to before: the resolver returns None, autodetect falls
through to the keyless ring, and no gateway request is made. A registered
zero-credit account still gets free fast search. Extract and every other managed
vendor keep the paid entitlement gate.
2026-10-01 23:55:54 +05:30
Robin Fernandes
fd24751a9f fix(web): serve free managed fast search to every Nous identity
The Tool Gateway serves Perplexity POST /search with search_type: "fast"
to any Nous identity without funding checks, but Hermes gated the route
locally behind managed_nous_tools_enabled() (paid access or a live tool
pool), and autodetect only selected managed Perplexity after the gated
Firecrawl probe passed. Anonymous and credit-exhausted users fell through
to the keyless ring without trying it.

Resolve the Perplexity search route on identity alone
(resolve_free_search_gateway: usable Nous token, same guest-disabled and
refresh rules), and let autodetect pick it when nothing explicit is
configured. Every other vendor keeps the entitlement gate, and the billed
managed-Firecrawl fallback now requires it too, so a free-search-only
caller whose call fails goes to the keyless rescue ring as before.
Explicit selections and direct keys keep precedence.
2026-10-01 23:55:54 +05:30
kshitijk4poor
615bf73b09 docs(codex): the app-server runtime runs the Hermes-selected model
Document that model.default and /model reach codex on thread/start and every turn/start, and that -900k variants go out as the base slug.
2026-10-01 23:33:52 +05:30
beardthelion
8444e9eb18 fix(ci): confine plugin-catalog admission gate to the pinned clone
The pinned-source job joined a PR-controlled subdir onto the clone root
with no validation, accepted file:// repos and ref names as the "pin",
and followed committed symlinks out of the clone, so the gate could
validate attacker-planted files instead of the declared commit's
contents. Harden the step end to end:

- validate repo (https://, whitespace-free), sha (40-hex, commit object),
  and subdir (plain relative path) in the parser before any use
- run the parser under python3 -I against installed ruamel.yaml so the
  PR checkout cannot shadow its imports, capture parser output, and
  reset shell variables per entry so a dead parser cannot replay the
  previous entry's fields
- confine PLUGIN_DIR and every symlink beneath it to the clone root
- include renamed and typechanged entry files in the changed set
- feed entries over fd 3 and keep plugin-executed commands off stdin so
  validate cannot drain the remaining list
- NUL-delimit both self-updater grep stages and sanitize the echoed
  filename list so crafted names cannot forge workflow commands
- mirror the same repo/subdir rules in the runtime catalog loader and
  the site extractor, and use \Z anchors so a trailing newline cannot
  split the verdict between gates
2026-10-01 10:46:45 -07:00
Mariano Nicolini
bf1bc78e63 feat(image_gen): expose the Krea 2 sliders to the agent only when the active backend declares them
(cherry picked from commit cbbd7f2b7b961fc822b47f4783b29fbc581f06b6)
2026-10-01 16:22:28 +05:30
Mariano Nicolini
87f3fa8881 fix(image_gen): cap local Krea style references by total size so multi-file requests fit the gateway limit
(cherry picked from commit 3e4e2bc4dbcd3143099455a9b604fb36af35f34a)
2026-10-01 16:22:28 +05:30
Mariano Nicolini
f5928fe003 fix(image_gen): embed local Krea style references so files from disk work outside a sandbox
(cherry picked from commit de6fe8e09002386786f6e64755d948a33c17bd89)
2026-10-01 16:22:28 +05:30
Mariano Nicolini
ff6b1c913f docs(tool-gateway): describe the single managed image picker and drop the per-call model flag that never existed
(cherry picked from commit 39c6f025b4c0391a14e8d667de106ba0e7fcc60a)
2026-10-01 16:22:28 +05:30
Brooklyn Nicholson
bdf4a5d632 chore: review follow-ups — stale timeout comments, softer docs claim, group-turn cap credits only user-blocked intervals 2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
d7fdebf45a docs: approvals.timeout and agent.clarify_timeout are the messaging-platform deadlines 2026-10-01 03:50:55 -05:00
Chukuwebuka-2003
b27d9eb7c0 feat(desktop): add review-pane diff scope selector (Uncommitted / Branch / Last turn)
The review pane docs promised three diff scopes, but the renderer hardcoded
'uncommitted', leaving Branch and Last turn unreachable even though the
Electron implementation and i18n strings were complete. Wire a scope picker
into the pane header and thread the chosen scope through the list/diff reads.

- Add $reviewScope (persisted) + $reviewTurnBase (HEAD-per-cwd map) atoms
- Thread scope + last-turn baseRef through refreshReview/selectReviewFile
- Capture HEAD at turn start per repo cwd, driven off $sessionStates so
  pinned tile worktrees and background sessions get their own baseline
- Gate stage/unstage/revert and the commit/PR ship bar to the uncommitted
  scope (branch/last-turn are read-only)
- Clarify the Last turn / read-only semantics in the desktop docs

Fixes #84347
2026-10-01 00:26:52 -05:00
Brooklyn Nicholson
6489ef982c fix(desktop): route Ctrl+W to the shell over an interactive terminal
Main claims Cmd/Ctrl+W on every window (window-accelerator's
before-input-event + preventDefault, kept for the #105498 swallow
guard and the Win/Linux close path) and routes it to the renderer via
hermes:close-preview-requested; the renderer's close rung maps any
focused [data-terminal] to closeActiveTerminal(), so the chord closed
the pane and killed the live shell instead of reaching xterm as
readline unix-word-rubout (#65457).

Route it the way #108550 routes Cmd/Ctrl+R: keep main's claim exactly
as-is, and add the missing terminal rung in the renderer.

- TerminalInstance (the user PTY) gains [data-interactive-terminal];
  the read-only agent mirror keeps only [data-terminal], so its tab
  stays deliberately closeable (the #65524 breadth complaint).
- The terminal handle registry (terminal-context-menu) keys by the
  [data-terminal] scope rather than the nested xterm host — the same
  fix #108550 carries — and grows a wordErase verb: the user terminal
  re-delivers the ^W byte to its PTY, the agent mirror's is null.
- The close-preview IPC rung (use-desktop-integrations) and the
  view.closeTab keybind dispatch (use-keybinds) both consume the
  bare platform chord through that verb instead of closing; Shift /
  Alt / rebound variants keep closing.
- The non-mac Window>Close menu item becomes click-only
  (registerAccelerator: false): the 'close' role's default
  CommandOrControl+W accelerator would claim the chord before the
  before-input-event run. Clicking it still closes the window.

Co-authored-by: Kyssta <218078013+kyssta-exe@users.noreply.github.com>
Co-authored-by: percival <ignacio@agenticolabs.io>
2026-09-30 23:04:06 -05:00
Brooklyn Nicholson
9c6fe3aa75 docs(desktop): keep the ARM64 window-context note inside the Troubleshooting section
The new ### heading was inserted between '### Troubleshooting' and its bullet
list, orphaning the dashboard sign-in bullets out of their section.
2026-09-30 22:27:11 -05:00
itsflownium
ff19f80f86 fix(desktop): make ARM64 binding guidance version-independent 2026-09-30 22:27:11 -05:00
itsflownium
9b363a33db fix(desktop): explain unavailable Windows ARM64 window context 2026-09-30 22:27:11 -05:00
unsupportedpastels
9c2d40d60a fix(gateway): preserve managed values and bridge authored subdicts
Restore managed extra values after cross-shape merges and expose authored subdict conflicts to plugin hooks before global env fallbacks. Consolidate the precedence coverage into two invariant matrices and qualify documentation for managed policy and explicit env precedence.

Co-authored-by: Victor Kyriazakos <victor@rocketfueldev.com>
2026-09-30 20:10:03 -04:00
Victor Kyriazakos
de63c6d275 fix(gateway): authored platforms.<plat>.extra wins over the top-level platform block
Replace _fill_extra with a _authored_wins overlay applied at BOTH copy
sites: the shared-key bridge and the plugin YAML hook. The hook now runs
on the overlaid block, so its YAML→env bridge seeds the authored nested
value into the env rung that env-first adapter readers resolve first.
Conflicting (platform, key) pairs warn once per load via a per-load set
threaded through both functions; _fill_extra and the module-level dedupe
set are gone. Docs note the precedence in slack.md.

(cherry picked from commit 39d047224ee7a6987d7b20ef92c8e707d29f9c70)
2026-09-30 20:10:03 -04:00
Brooklyn Nicholson
6b2fe92af6 docs(desktop): describe favorite models in the picker 2026-09-30 17:31:21 -05:00
Brooklyn Nicholson
641af6dfc5 feat(desktop): shift-click a model to pin it to the top of the picker
Pin a model the way you pin a chat: shift-click its row, or choose Pin in
its options submenu. It lifts into a Pinned section above the provider
groups, in the order you pinned it, with the provider named on the row
so two labs sharing a model id stay apart. Shift-click neither picks the
model nor closes the menu, so a second shift-click (on the row, now in
the Pinned section) unpins it; a plain click still selects. That section
is the row's only place in the menu, with no duplicate down in its
provider group, and a search is exempt, since a query means "list every
match in its provider's place".

With nothing pinned there is no empty section. The gesture is taught
where the action lives instead: the submenu's Pin item carries the
shift-click hint in the menu's shortcut slot.

The catalog owns the preference like it owns the Edit Models shortlist,
so the composer pill, session tiles and the kanban override all show
the same section. The per-family row moves into a shared ModelFamilyRow
so the Pinned section and the provider groups can never paint a model
differently, and the keyboard row list mirrors the render order (pins
first). A pin whose provider is not connected paints no row and is kept,
not dropped; pins ignore the shortlist because a pin IS an explicit
"always show me this one". The submenu stays pure: the pin row only
paints when the host passes onTogglePin, so the reasoning pill (which
has no catalog behind it) is unchanged.

Co-authored-by: Jahaziel Hernandez <jahaziel.hernandez@baubap.com>
2026-09-30 17:31:21 -05:00
brooklyn!
bda33b601d Merge pull request #128992 from NousResearch/bb/bot-focus
fix(bot-mode): make bot-to-bot messaging, Bot Chat opens, and lease handoff actually work
2026-09-30 14:40:57 -05:00
Victor Kyriazakos
60ce574905 fix(fast): send service_tier on the openai-api route and accept operator OpenAI tiers
Treat openai-api as the first-party api.openai.com route in _fast_mode_route_supported. This closes the route gate introduced by c7e2e0b779 while preserving the existing host check, so proxies and aggregators remain excluded. The shared service_tier parser already accepts operator literals, including ultrafast, and the existing static and bounded-mode paths thread the selected literal through request_overrides.
2026-09-30 14:23:51 -04:00
Brooklyn Nicholson
9a9489ca1b docs(bot-mode): answer "what does this add over profiles?"
The top question since launch. The page said a Bot is a profile and stopped
there; spell out what the roster, Bot Chat, messaging, and routines add.
2026-09-30 09:57:33 -05:00
Austin Pickett
a35f600b8a fix(desktop): resolve the Windows ssh client instead of hard-coding System32 OpenSSH
All ssh spawns (the -G config probe, the ssh-config:resolve IPC, SSH
terminals, and SshConnection) now go through one pure resolver:
desktop.ssh_path in config.yaml, then System32 OpenSSH, then Git for
Windows' usr\bin\ssh.exe, then bare ssh. Non-Windows keeps bare ssh.

A failed local ssh -G probe is now tagged terminal: boot latches on the
failure overlay (naming the client and the ssh_path setting) instead of
re-driving the same probe every ~2s.

Fixes #103288
2026-09-29 22:56:15 -04:00
ethernet
94f3f170d4 Merge pull request #128428 from NousResearch/ethie/desktop-workspace-follow-and-git-meta
fix(desktop): sessions follow the agent's worktree and record their git branch
2026-09-29 17:56:12 -04:00
Ritvik Gautam
6776a429b5 fix: use original gateway text for automatic session titles
Gateway turns reach the agent enriched with channel-bound skill payloads,
sender metadata and other model context, so the first user message the
titler saw could be thousands of characters of skill text ahead of the
actual question, and new sessions were named after the skill instead of
the request.

The gateway now captures the inbound event text before that enrichment and
passes it as a turn-local title_user_message through TurnContext, the turn
runner, AIAgent.run_conversation and the conversation prologue. Only the
titler reads it: the main model, persisted history and relay metadata still
receive the full enriched message. An explicit empty or whitespace override
suppresses titling for that turn, None keeps the existing model-facing
fallback (attachment-only turns, paste previews), and the override cannot
enable model titling for cron or subagent runs. The value is a per-call
argument, so a cached agent never reuses a previous turn's override.

Rebased onto current main: the gateway call keeps main's pinned channel
prompt for internal events, and the integration fixture's context-prompt
stub accepts main's internal= keyword.

Ritvik Gautam made this change with Doryani AI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:57:52 -05:00
Austin Pickett
7cadfaa668 fix(api_server): honour include_compacted on /api/sessions/{id}/messages
The gateway REST messages endpoint parsed limit/offset/order but never
read include_compacted, so clients always got the live post-compaction
transcript. Parse it like the dashboard router and pass it through to
SessionDB.get_messages.

Fixes #101939
2026-09-29 16:30:33 -04:00
ethernet
65cfee332a fix(tui): follow a chat into the worktree its agent settled in
An agent told to make a worktree and work in it left the chat on the checkout it
started in — the sidebar kept the chat in the primary branch lane while every
later command ran in the worktree.

`_reconcile_session_cwd_from_terminal` refused whenever `explicit_cwd` was set,
but `session.create` sets that for ANY session whose cwd exists on disk, and the
desktop always sends a workspace. The guard that was meant to protect a
deliberately chosen workspace (#72776) therefore disabled the follow for the
whole desktop surface.

Key the pin on `cwd_pinned`, set only by the two moves the user drives:
`session.cwd.set` (the composer's folder picker) and `session.workspace.move`
(move-to-project). A cwd a chat merely STARTED in is not a pin, so the agent's
own worktree settle re-anchors the chat and the desktop moves it to that lane.
2026-09-29 16:29:04 -04:00
Jeffrey Quesnelle
dccb84b924 Merge pull request #115343 from marcusds/chore/relay-0.9
chore(relay): upgrade to 0.9.3
2026-09-29 15:05:57 -04:00
unsupportedpastels
9ec9028e50 fix(checkpoints): label checkpoints that did not capture a nested repo
A nested repository is stored as a gitlink, so its files are not in the
checkpoint. Say so when the checkpoint is taken: its message gains
"[nested git repos not captured: <paths>]", which every /rollback listing
(CLI, gateway, TUI, Desktop) already shows. The gitlink parse is shared
with the rollback guard.

rollback.list sent the TUI an always-empty message (it read a "message"
key the manager calls "reason"), so TUI and Desktop listings never showed
what a checkpoint was; map it from reason.

Co-authored-by: FroRaut <79215537+Froraut@users.noreply.github.com>
2026-09-29 14:12:41 -04:00
unsupportedpastels
e700e5ffdb fix(checkpoints): honor Git pathspecs in nested repository guard
Resolve selective restores against a disposable index before any checkpoint mutation, preserving unrelated-file recovery and refusing uncaptured gitlinks. Keep NUL-delimited filenames lossless, including surrogateescaped POSIX bytes. Add real-Git regression coverage and document the capture limitation.

Fixes #127073
Salvages #127077

Co-authored-by: Yuan Li <dskwelmcy@163.com>
2026-09-29 14:12:41 -04:00
emozilla
51aa249efd feat(relay): name the Relay plugin files in doctor and the startup log
Relay 0.9 discovers user and system plugins.toml files outside the Hermes
home, so an exporter or middleware can apply to every Hermes session without
any Hermes setting naming it. Hermes now says which files apply.

The startup line that reports an active plugin host lists the files the
activation loaded. `hermes doctor` gains a NeMo Relay Plugins section: it
resolves the same layers through Relay's validator, which loads no plugin
code and leaves the process-wide host alone, then lists each file, whether
anything is enabled, and any error or warning Relay reports. A configuration
Relay would reject counts as a doctor issue. Validation cannot load dynamic
plugins, so a warning marks the row without counting as an issue.

The docs give the user and system file locations for each platform and point
to `hermes doctor`.
2026-09-29 12:16:43 -04:00
Gille
85b01aac9b fix(cli): diagnose stale toolsets and disabled platforms
(cherry picked from commit 98ab863de43995a133b2ccacdc790fb3589128ec)
2026-09-29 20:34:45 +05:30
Siddharth Balyan
5eea87882a Clarify: one question shape and one result shape on every surface (skip, cancel, timeout and undelivered are told apart) (#127760)
* refactor(desktop): split clarify-tool.tsx into a clarify/ folder

Pure moves, no behaviour change. Parsing, the question-card core
(shell, choice rows, question block), the delivery watchdog, and each
pending/settled card get their own file so the core can be reused.

* feat(clarify): one questions[] shape with per-question status and one outcome

The clarify tool now takes only questions=[{question, choices?, multi_select?}].
A wrong shape is a tool error that names the right one, so a model that
sends the old top-level question/choices corrects itself on the next call.

Every result has the same shape on every surface: each response carries
status (answered, skipped, unanswered) with user_response null unless
answered, and the result carries one outcome (submitted, cancelled,
timed_out, undelivered) plus an optional surface-written notice. The
timeout and cancel sentences that used to pose as the user's answer are
gone, and the compressor reads status instead of matching their prefixes.

The callback contract is callback(questions) -> {answers, outcome, notice?}
(None = skipped, missing qid = unanswered). tui_gateway settles a batch the
same way for the last lock, a cancel, an interrupt and the deadline, and
clarify.lock keeps a null answer as a skip. A multi-select answer that is
not a JSON array counts as one typed ("Other") answer. The tool-row preview
reads the first question.

* feat(clarify): every surface asks through the one question card

CLI: the batch panel is the only panel; Enter on an empty field skips a
question, Ctrl+C cancels and keeps the locked answers, the deadline returns
timed_out. -q and -z return undelivered with a no-user notice.

Ink TUI: the single-question prompt is gone; the card supports multi-select
(Space or a digit toggles, Enter locks, typed Other text joins the picks),
an empty submit skips, Esc cancels, and "Batch" names are dropped.

Desktop: the single-question card is deleted and its keyboard handling moved
into the one card (arrows, letters and digits, auto-advance, Enter picks then
confirms). Confirm enables at one answer; blank questions lock null; Skip and
a composer message cancel; the settled card shows No answer for unanswered
questions. The bots room card follows the same contract.

Messaging: one card per question with a "Reply skip to skip" line in every
locale; timeouts and delivery failures map to timed_out and undelivered.

* fix(clarify): cancelled for a released messaging card, labels win over the skip word

A prose reply to a messaging choice card, /new and session end released
the wait with "", which read as timed_out. They now resolve with a
CANCELLED marker and the tool gets outcome cancelled.

A typed reply that matches a choice label ("Skip") now resolves to that
choice; the skip word applies only when no choice matches.

The bots room card sends the picked labels as they are; the tool already
strips the recommendation label. Unused OUTCOMES and a new docstring and
comment are removed.

* fix(tui): re-editing a multi-select answer keeps its picks

The Ink card stored a multi-select answer as display text ("A, B"), so
revisiting the question put the whole string into Other and re-locked it
as one item. The answers map now keeps the raw JSON array, the card
restores the picks on revisit, and only the display lines format it.

Comments that earlier edits reworded are restored to their original
words, minus the phrases the change made wrong. The compute-host clarify
lock accepts None for a skipped question.

* test(clarify): align three checks with the one-answer confirm and the regenerated keys

The desktop Cmd+Enter test now expects a send with one answer (the blank
question locks null), the compressor test expects the single-answer summary
the code gives, and locales/_keys.desktop.json is regenerated for the
removed and added clarify strings.

* test(tui): the one-question card header is singular
2026-09-29 18:33:44 +05:30