cae6b5486fec8e9b5f366ea3ed5254d53fbc341d
962 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| cae6b5486f |
feat(dashboard): always enable embedded chat; remove dashboard --tui flag
The dashboard's embedded Chat surface (/chat, /api/ws, /api/pty) was gated behind `hermes dashboard --tui` / HERMES_DASHBOARD_TUI=1. The desktop app and the dashboard's own Chat tab both drive the agent over the /api/ws + /api/pty WebSockets, so a dashboard started without the flag would pass the /api/status health check but slam the chat WebSocket shut with WS code 4403 — the app connects, reports "ready", and chat stays dead. This was the root cause behind multiple user reports of the desktop app failing to connect to a self-hosted gateway/dashboard, and it bit Docker and host installs alike. Make the embedded chat unconditional: - web_server.py: _DASHBOARD_EMBEDDED_CHAT_ENABLED defaults to True; drop the embedded_chat parameter and the runtime reassignment from start_server(). The WS gates still read the constant (now always true) so the seam — and its "rejects when disabled" contract test — stays meaningful. - main.py: remove the `--tui` argument from the dashboard subparser and the `embedded_chat = args.tui or HERMES_DASHBOARD_TUI==1` derivation. - web/: isDashboardEmbeddedChatEnabled() returns true unconditionally; drop the deprecated __HERMES_DASHBOARD_TUI__ alias and the dead LEGACY_TUI_RE scrape in the vite dev-token plugin. - apps/desktop/electron/main.cjs: drop `--tui` from the spawned dashboardArgs (it would now error with "unrecognized arguments: --tui") and the redundant HERMES_DASHBOARD_TUI env injection. - Docker: no s6 run-script change needed — the script never passed --tui; the HERMES_DASHBOARD_TUI env var is now simply a no-op, so the image works out of the box with no extra var. - Docs: remove every dashboard --tui / HERMES_DASHBOARD_TUI reference across the CLI reference, env-var reference, docker/desktop/web-dashboard guides, in-app tips, and the zh-Hans translations. The terminal `hermes --tui` / HERMES_TUI references are intentionally left untouched. Tests: 270 passing across web_server, dashboard lifecycle, host-header, auth-gate, and docker-override-scripts suites. |
|||
| b36a30db20 |
docs(dashboard-auth): document the username/password provider
Add a 'Username/password provider (no OAuth IDP)' section to the web dashboard guide (config.yaml + env surfaces, the explicit-secret caveat, the rate-limit/generic-401 properties, and a 'write your own password provider' pointer to the supports_password extension point), and list the HERMES_DASHBOARD_BASIC_AUTH_* env vars in the environment-variables reference. |
|||
| 0401176c7a |
Merge pull request #38760 from helix4u/fix/prefill-config-compat
fix(config): align prefill messages key handling |
|||
| f31c950182 |
refactor(supermemory): session-level ingest + kebab aliases (salvaged from #32487) (#38756)
* refactor(supermemory): session-level conversation ingest + kebab tool aliases Salvaged from #32487 (by @MaheshtheDev), rebased onto current main. - sync_turn now buffers cleaned turns; the full session is ingested once at session end / switch / shutdown via the conversations endpoint - ingest_conversation() accepts and forwards functional document metadata (type, session_id, message_count, partial) - register kebab-case tool aliases (supermemory-save/search/forget/profile) alongside the snake_case names - README + docs (EN/zh-Hans) updated for the simplified session model Source/vendor-attribution removed per project policy (no telemetry): dropped x-sm-source header, sm_source metadata, and sm_capture_mode tags. Preserved the post-branch atomic_json_write(mode=0o600) hardening that the PR's stale base had reverted. Updated provider tests for the new behavior and added maheshthedev@gmail.com to release.py AUTHOR_MAP. Co-authored-by: alt-glitch <balyan.sid@gmail.com> * feat(supermemory): restore x-sm-source for Spaces routing Reinstates x-sm-source: hermes (SDK default_headers + conversations POST) and sm_source: hermes document metadata. Per @Dhravya (Supermemory), this is a functional routing key, not telemetry: it groups Hermes writes into a dedicated "Hermes" Space in the Supermemory app so users can filter and bulk-manage memories per source agent. sm_capture_mode remains dropped (appears analytics-only; Spaces are routed by sm_source) pending confirmation. Adds README note + a unit test covering _merge_metadata sm_source stamping and legacy source->type migration. --------- Co-authored-by: Mahesh Sanikommu <maheshthedev@gmail.com> |
|||
| ffb53767bf | fix(config): align prefill messages key handling | |||
| 04d620d91f |
fix(docker): run config migrations during container boot (salvage #35508) (#36627)
Salvage of #35508 (@dchenk), rebased onto current main. Resolved the tests/tools/test_stage2_hook_puid_pgid.py conflict (kept both the envdir-creation regression test on main and the new config-migration tests). Docker image upgrades replace code under $INSTALL_DIR but preserve $HERMES_HOME on the mounted volume, so the persisted config.yaml never received the schema migrations that non-Docker `hermes update` runs (#35406). This adds scripts/docker_config_migrate.py, invoked from stage2-hook after first-boot seeding and before gateway services start: it backs up config.yaml + .env, runs migrate_config(interactive=False), and honors HERMES_SKIP_CONFIG_MIGRATION=1 for manual control. Also fixes a latent bug in check_config_version(): it called load_config() which deep-merges DEFAULT_CONFIG, so a legacy config with no raw _config_version falsely reported as already-current. It now reads the raw on-disk file so legacy configs are correctly detected for migration. Differs from #35508 as submitted (Option B cleanup): dropped the `_config_version` line added to cli-config.yaml.example and removed the accompanying test_cli_config_example_declares_latest_version change-detector test. The example is a copy-template and has no business asserting a schema version; check_config_version() reads the user's real config.yaml, not the example. This removes a second sync point that drifts on every version bump. Closes #35508. Fixes #35406. Co-authored-by: Dmitriy Cherchenko <17372886+dchenk@users.noreply.github.com> |
|||
| ec69c767ff |
docs(desktop): point Chat section to remote-backend + dashboard doc (#38545)
The Desktop Chat section described chat-only and gave no signpost that remote-hosted Hermes connection is documented. Adds a pointer to the in-page remote-backend section and to the deeper Web Dashboard doc. |
|||
| 63727f32bf |
docs(dashboard): document connecting Hermes Desktop to a remote backend (#38534)
Desktop's readiness probe only checks GET /api/status (public), but the live chat rides /api/ws, which is gated by --tui (4403), a matching session token (4401), and a non-loopback bind. The web-dashboard doc covered --tui and the OAuth gate but never the Desktop remote-connection flow, so the three independent failure modes weren't documented together. Adds a 'Connecting Hermes Desktop to a remote backend' section: pin HERMES_DASHBOARD_SESSION_TOKEN, run with --host 0.0.0.0 --insecure --tui, the curl token-verification one-liner, and WS close-code triage. |
|||
| 51a2c07016 | fix(skills): document xurl X Article ingestion | |||
| 26a57467a8 |
fix(cli): harden hermes portal SystemExit handling + finish model-pick doc sweep
Self-review of #38465 surfaced three real items: 1. SystemExit escape (defense): `_login_nous` raises SystemExit(130)/(1) on cancel/failure. The logged-out login path inside `_model_flow_nous` catches it, but the expired-session re-login path (main.py) only catches Exception, so a Ctrl-C during re-auth could propagate past `_run_portal_one_shot` and kill the CLI. Add SystemExit to the portal handler so all cancel/abort cases end with the graceful 'Setup cancelled / retry later' message. 2. Doc sweep: the model-pick step was only added to the bare-`hermes portal` prose. Propagate it to the surfaces describing `hermes setup --portal` behavior that still omitted model selection: - `--portal` argparse help (main.py) - nous-portal.md intro + the numbered 'what it does' step list (EN + zh-Hans) - run-hermes-with-nous-portal.md 'default model after setup --portal' line, which was now contradictory (there's a picker, not a forced default) (EN + zh) 3. Test coverage: add parametrized regression test asserting the portal handler swallows KeyboardInterrupt / EOFError / SystemExit (returns None, no escape). Note on 'Skip (keep current)': delegating to _model_flow_nous means picking Skip preserves the prior provider instead of force-switching to nous — this is intentional and matches quick setup exactly; docs now say 'sets Nous as your provider (when you pick a model)' rather than unconditionally. |
|||
| cd188b814e |
feat(cli): make hermes portal run the full quick-setup Nous flow (model picker)
`hermes portal` / `hermes setup --portal` previously logged in and set provider=nous but left the model UNSELECTED (blank -> runtime default) and never showed a picker — unlike the first-time quick setup, which runs the model picker. Route `_run_portal_one_shot` through `_model_flow_nous` — the exact same routine quick setup (`_run_first_time_quick_setup`) and `hermes model` -> Nous use. It handles both the logged-out path (device-code OAuth, which picks a model internally) and the logged-in path (curated Nous model picker), then offers the Tool Gateway opt-in and sets provider=nous. Net effect: `hermes portal` now offers a model picker every time and is a true single-command collapse of quick setup's Nous step. Removes the hand-rolled auth_add_command + manual provider write + separate Tool Gateway prompt (now a single source of truth). Re-syncs the in-memory config from disk afterward so a caller's later save_config can't clobber the model/provider written by the login flow. Docs (CLI help, portal_cli docstrings, nous-portal EN + zh-Hans) updated to mention model selection. New regression test asserts `_run_portal_one_shot` delegates to `_model_flow_nous`. Verified live: `hermes portal` now shows the 27-model curated picker, 'Skip (keep current)' preserves prior provider/model. |
|||
| da4f407e51 |
feat(cli): make hermes portal the human-readable Portal onboarding alias
`hermes portal` (no subcommand) now runs the one-shot Nous Portal onboarding — OAuth login, switch provider to Nous, offer Tool Gateway — identical to `hermes setup --portal` and the human-readable alias for `hermes auth add nous --type oauth` (which still works). The prior status default moves to `hermes portal info`; `status` is kept as a hidden back-compat alias. `open`/`tools` subcommands are unchanged. User-facing hints and docs (status.py, conversation_loop 401 guidance, SystemPage, README, website docs + zh-Hans) now point at `hermes portal` / `hermes portal info`. `--manual-paste` references keep the explicit auth command since `hermes portal` does not expose that flag. |
|||
| 3c73d1852e |
docs: remote desktop connect needs --tui on the backend (#38350)
The Desktop App and Web Dashboard remote-connect instructions told users to start the backend with `hermes dashboard --no-open --insecure --host 0.0.0.0`, omitting --tui. Without --tui the embedded-chat WebSockets (/api/ws, /api/pty) are refused, so the desktop passes the /api/status health check and reports the backend "ready" — but chat never works because the socket is closed on connect. - Add --tui to both backend command blocks (with an inline why-comment). - Explain that the desktop chat runs over /api/ws + /api/pty and needs the embedded-chat surface enabled; a plain dashboard/gateway is not enough. - Add a troubleshooting entry for the exact symptom (connects, says ready, chat dead) on both pages. |
|||
| 0d9b7132ff |
feat(observability): observer-grade telemetry hooks + NeMo-Relay plugin
Adds backend-neutral observer hooks for plugins: session, turn, API request, tool, approval, and subagent lifecycle events with stable correlation IDs (session_id, task_id, turn_id, api_request_id, tool_call_id, parent/child subagent ids). Extends VALID_HOOKS with api_request_error and subagent_start. Hot path is zero-cost when no plugin subscribes: has_hook()/presence checks gate all payload construction, request payloads are returned by reference when no middleware rewrites, and the sanitized response payload no longer embeds raw response objects. Bundles the optional NeMo-Relay observability plugin (plugins/observability/nemo_relay) as an in-repo consumer of the new hooks, peer to the existing langfuse plugin. Fails open when the optional nemo-relay package is not installed. Authored-by: Bryan Bednarski <bbednarski@nvidia.com> Salvaged from #29722 onto current main. |
|||
| c5d199eada |
feat(dashboard): check-before-update flow on the System page (#38205)
The dashboard's update button ran 'hermes update' immediately with no preview. Now the System page shows whether an update is available and asks the user to confirm before applying it. - New GET /api/hermes/update/check: reports install method, current version, and commits-behind (via banner.check_for_updates, 6h-cached; ?force=1 busts the cache). Soft-fails to behind=null on network error; marks docker/nix/homebrew as can_apply=false with the out-of-band cmd. - System page: update-status badge on the Hermes version row (latest / N behind), a Check-for-updates button, and an Update-now button that opens a ConfirmDialog showing the commit count before POST /api/hermes/ update fires. Cached status loads with the rest of the page. - Docs + 5 endpoint tests (git/up-to-date/docker/soft-failure + auth gate). |
|||
| 1b302a0474 |
feat(debug): include desktop.log in hermes debug share / /debug / hermes logs (#38203)
The Electron desktop app writes boot failures, backend spawn output, and Python tracebacks to HERMES_HOME/logs/desktop.log, but debug-share only captured agent/errors/gateway — so desktop boot issues never made it into shared debug reports. - logs.py: register desktop -> desktop.log (enables 'hermes logs desktop') - debug.py: capture desktop snapshot, add to summary report, upload full desktop.log in 'share', update privacy notice - gateway /debug inherits the desktop tail via collect_debug_report() - main.py + docs: help text and log-name table (also adds missing gui row) - tests: desktop seed in fixture, new report test, three_pastes -> four_pastes |
|||
| ef65298103 |
docs: make the Desktop App remote-backend section self-contained (#38194)
The section explained why the Session token is hidden but punted the actual setup steps to the web-dashboard page via a link — a bounce for someone on the Desktop App page trying to connect. Inline the concrete steps instead: backend command block (mint token -> .env -> hermes dashboard --insecure), the in-app Remote gateway steps, the env-var override, Tailscale guidance, and a troubleshooting list. Keep a short pointer to the web-dashboard page for the same setup from that angle. |
|||
| d833b1eff7 |
docs: add remote-backend section to the Desktop App page (#38180)
The Desktop App page covered install, settings, and chat but not how to connect the app to a backend on another machine — the exact thing @PedjaDrazic asked about. Add a 'Connecting to a remote backend' section that explains the Session token is the dashboard token Hermes never surfaces (pin it via HERMES_DASHBOARD_SESSION_TOKEN + run --insecure), and link to the web-dashboard page for the full backend setup rather than duplicating it. Add a reciprocal link from the web-dashboard remote section back to the Desktop App page. |
|||
| 0022e94d74 | feat(matrix): support bang command aliases | |||
| 6038bfb66e |
docs: explain remote-gateway session token for Hermes Desktop (#38144)
The desktop Remote gateway field asks for a session token that Hermes never surfaces — by default web_server.py mints an ephemeral token per boot and injects it into the served HTML, so there is nothing in config.yaml, /gateway, or env to copy. Document that you pin it yourself via HERMES_DASHBOARD_SESSION_TOKEN, run the backend with --insecure (keeps the legacy token auth path instead of engaging the OAuth gate), then paste that value into the desktop app. - web-dashboard.md: new 'Connecting Hermes Desktop to a remote backend' section (backend + desktop steps, --insecure vs OAuth-gate nuance, HERMES_DESKTOP_* env override, Tailscale guidance, troubleshooting). - environment-variables.md: new 'Web Dashboard & Hermes Desktop' env-var table (HERMES_DASHBOARD_SESSION_TOKEN, HERMES_DESKTOP_REMOTE_URL/TOKEN, the OAuth and public-url vars) — none were previously documented. |
|||
| 047e7cf36f |
fix(docs): remove remaining stale submodule references missed by #38089 (#38105)
Follow-up to #38089. The merged PR removed --recurse-submodules from the installer, CI, and getting-started docs, but missed the same stale clause in: - CONTRIBUTING.md (Prerequisites table) - website/docs/developer-guide/contributing.md (table + clone command) - zh-Hans mirror of the developer-guide contributing doc git-lfs is kept in the Git requirement rows since it's a separate, real prerequisite. No .gitmodules has existed since the Atropos RL submodule was removed in #26106. |
|||
| 43fd63b4b5 |
fix(windows): rip out unused submodule support in installer & docker & docs
we have no submodules anymore, so #37702 was kinda right, but we can just delete it entirely. |
|||
| d9f7e7ac81 |
fix(docker): seed gateway_state.json from HERMES_GATEWAY_BOOTSTRAP_STATE on first boot (#37896)
On a fresh volume there is no gateway_state.json, so the boot reconciler
(cont-init.d/02-reconcile-profiles) registers the gateway-default s6 slot
but leaves it down — it only auto-starts when the last recorded state was
"running". A freshly-provisioned container therefore comes up with the
gateway down until something starts it (e.g. the dashboard's start button).
Add a generic, first-boot-only env-seed in stage2-hook.sh (which runs
before 02-reconcile-profiles): when HERMES_GATEWAY_BOOTSTRAP_STATE=running
and no gateway_state.json exists yet, seed {"gateway_state":"running"} so
the reconciler brings the supervised slot up on the very first boot.
This mirrors the existing HERMES_AUTH_JSON_BOOTSTRAP pattern: it seeds the
same state file the reconciler already consults, guarded by [ ! -f ] so
persisted runtime state always wins on later boots (a deliberately-stopped
gateway stays stopped across restarts). Only the literal "running" is
honoured (the sole value in the reconciler's _AUTOSTART_STATES).
Generic container contract — no host-specific code. Useful to any
orchestrator that provisions a blank volume and wants the gateway up from
first boot (the supervised gateway/dashboard already work on such hosts;
only the first-boot autostart was missing because the CLI lifecycle
commands can't drive the s6 layer when container self-detection misses).
Adds a shell-level contract test and documents the env var.
|
|||
| d6b0c23f87 |
feat(cli): configurable default interface (cli vs tui)
Add `display.interface` config key so users can make the modern TUI the
default for bare `hermes` / `hermes chat` without exporting HERMES_TUI=1 in
every shell. Default stays "cli" to preserve current behavior.
Add a `--cli` flag (mirrors `--tui`) so an explicit invocation can force the
classic prompt_toolkit REPL even when `display.interface: tui` is configured.
Precedence (highest first): `--cli` > `--tui`/`HERMES_TUI=1` > config
`display.interface` > classic REPL. Two resolvers enforce it:
* `_resolve_use_tui(args)` — the args-aware resolver used by `cmd_chat`
and the Termux fast-TUI path (uses full load_config()).
* `_wants_tui_early(argv)` — a dependency-free early resolver used by
mouse-residue suppression and the Termux fast paths, which run before
argparse / hermes_cli.config are importable (minimal cached YAML read).
Both `--cli` and `--tui` are registered via `_inherited_flag`, so they are
carried across self-relaunch automatically.
- config: add display.interface ("cli" default), bump _config_version 25->26.
The generic missing-field migration + load_config() deep-merge seed the key
for existing configs; no bespoke migration block needed.
- docs: document --cli flag and display.interface in cli-commands.md and
the TUI user guide.
- tests: new test_default_interface_resolution.py covering resolver
precedence at every layer, early resolver edge cases (missing/garbage
config), parser flags, and relaunch inheritance.
|
|||
| c2050183a5 |
feat(desktop): content-hash build stamp with --build-only and --force-build flags
Add a SHA-256 content-hash based build stamp to `hermes desktop` so unchanged source trees skip the npm install + build step. Uses pathspec for .gitignore-aware file matching instead of a hardcoded skip-list. New CLI flags: - --build-only: run the build but don't launch the app - --force-build: rebuild even when the stamp matches `hermes update` now calls `hermes desktop --build-only` so the desktop app is rebuilt (if needed) as part of the update flow. 16/16 tests passing. |
|||
| 7450bee8bc | fix(docs): update desktop app docs | |||
| 3eb6bd7f92 |
docs: add Desktop App guide (#37457)
The native Electron desktop app shipped (PR #20059 and follow-ups) but the docs only told people how to download it, not what it is or how to use it. Adds website/docs/user-guide/desktop.md covering install (installer + prebuilt + Windows GUI), the chat-first UI and management panes, the hermes desktop CLI flag reference, self-update, how-it-works, and troubleshooting. Sourced from apps/desktop/README.md, routes.ts, and the real argparse. Wired into sidebars.ts under Interfaces after the TUI. |
|||
| 3c1d066a8a |
feat(dashboard): Channels page — set up every gateway messaging channel from the browser (#37211)
The /api/messaging/platforms endpoints (catalog, configure, test) shipped with the desktop app but never got a dashboard UI; the recent admin-panel PRs covered MCP/webhooks/hooks/system but skipped messaging channels. This adds the missing page so all 20+ channels (Telegram, Discord, Slack, Matrix, Mattermost, WhatsApp, Signal, BlueBubbles, Email, SMS, DingTalk, Feishu, WeCom, WeChat, QQ Bot, Yuanbao, plugin platforms, etc.) can be configured, enabled/disabled, tested, and connected entirely from the browser. - web/src/pages/ChannelsPage.tsx: per-platform list with live status, enable Switch, Test, and a Configure modal that renders each platform's exact setup fields (secrets masked, required validated, redacted display). - web/src/lib/api.ts: MessagingPlatform types + get/update/test client fns. - web/src/App.tsx: /channels route + nav tab (Radio icon, after MCP). - docs: Channels section + REST endpoints + screenshot. Frontend-only — reuses the existing env-write + config-enable backend, which auto-enables a platform once its required env vars are present and the gateway restarts. No core changes, no new tool schema. |
|||
| bd8e2ec1a6 |
feat(dashboard): complete admin panel — MCP catalog, enable/disable toggles, hook creation, system stats (#36736)
* feat(dashboard): MCP catalog + enable/disable, webhook toggle, hook create/delete, system stats
Backend for the comprehensive admin pass:
- MCP: GET /api/mcp/catalog (browse Nous-approved optional-mcps), POST
/api/mcp/catalog/install, PUT /api/mcp/servers/{name}/enabled
- Webhooks: PUT /api/webhooks/{name}/enabled; gateway rejects disabled routes
with 403 (hot-reloaded, no restart)
- Hooks: POST/DELETE /api/ops/hooks — create (with consent approval) + remove;
list now reports accurate allowlist status + valid events
- System: GET /api/system/stats — OS/arch/python/cpu + psutil memory/disk/
uptime/process, stdlib fallback
All gated by dashboard auth; secrets never returned.
* feat(dashboard): MCP catalog UI, enable/disable toggles, hook create, system stats
- McpPage: catalog section (browse Nous-approved MCPs, one-click install with
env prompts) + per-server enable/disable toggle with gateway-restart note
- WebhooksPage: per-subscription enable/disable toggle (muted + badge when off)
- SystemPage: new Host stats section (OS/arch/python/cpu/mem/disk/uptime/load),
shell-hook create modal + delete, 'Create backup' label
- api.ts: client methods + types for catalog, toggles, hook CRUD, system stats
* test(dashboard): cover catalog, toggles, hook CRUD, system stats, webhook toggle
Adds tests for the comprehensive pass: MCP enable/disable + catalog list +
catalog-install-unknown, hook create/delete with consent, system stats shape,
and webhook enable/disable. 26 tests total, all green.
* docs(dashboard): document the comprehensive admin pass + fresh screenshots
Updates the MCP/Webhooks/Pairing/System sections for catalog browse+install,
enable/disable toggles, hook creation, and host system stats; adds the new
endpoints to the API table; replaces the screenshots with live captures of
the rebuilt pages (real data, no dummies) including the hook-create modal.
* feat(dashboard): curator, portal status, and prompt-size/dump/migrate ops
Closes the last in-scope CLI gaps from the coverage audit:
- Curator: GET /api/curator (status), PUT /api/curator/paused, POST
/api/curator/run (background)
- Portal: GET /api/portal (Nous auth + Tool Gateway routing, read-only)
- Diagnostics: POST /api/ops/prompt-size, /api/ops/dump, /api/ops/config-migrate
(backgrounded, tailed via action status)
Host-bound commands (secrets/proxy/lsp/acp/computer-use/desktop/completion/
postinstall/uninstall/claw) remain CLI-only by design.
* feat(dashboard): curator + portal + diagnostics UI, tests
- SystemPage: Nous Portal status section (auth + Tool Gateway routing),
Skill curator card (status + pause/resume + run now), and three new
Operations buttons (prompt size, support dump, migrate config)
- api.ts: client methods + CuratorStatus/PortalStatus types
- tests: curator pause/resume, portal shape, system-stats shape, + auth-gate
coverage for the new GET endpoints (31 tests total)
* docs(dashboard): document curator, portal, and diagnostics + refresh System screenshots
Updates the System section for the Nous Portal status, Skill curator
controls, and the new prompt-size/dump/migrate operations; adds them to the
API table; refreshes the System screenshots (now showing Portal + Curator)
and adds a dedicated curator/gateway/memory capture.
* feat(dashboard): session stats/export/prune + skills hub search endpoints
Completes the existing tabs' backend depth (audit vs CLI):
- Sessions: GET /api/sessions/stats (store stats), GET /api/sessions/{id}/export,
POST /api/sessions/prune. /stats is registered before /{session_id} so the
literal path isn't captured by the parameterized route.
- Skills: GET /api/skills/hub/search — parallel multi-source hub search (threaded),
returns installable identifiers
- (rename via PATCH and cron-edit via PUT already existed; now surfaced in UI)
* feat(dashboard): complete existing tabs — sessions mgmt, skills hub browse, cron edit
Audited every existing tab against its CLI command and filled the gaps:
- Sessions: store stats bar, per-row rename + export (JSON download), and a
prune-old-sessions control (mirrors hermes sessions rename/export/prune/stats)
- Skills: new 'Browse hub' view — search the skill hub across all sources,
install by identifier with a live install log, and 'Update all' (mirrors
hermes skills search/install/update)
- Cron: per-job Edit modal (pre-filled) calling updateCronJob (hermes cron edit)
- api.ts: renameSession/getSessionStats/exportSessionUrl/pruneSessions,
updateCronJob, searchSkillsHub + types
Models tab was already comprehensive (provider+model picker, dynamic per-provider
lists, main + all 11 aux-task assignments, reset) — verified, no change needed.
* test(dashboard): cover session stats/rename/export/prune + skills hub search
Adds the route-shadowing guard for /api/sessions/stats (must not be captured
by /api/sessions/{session_id}), rename/export/prune, and the empty-query
short-circuit for hub search. 36 tests total, all green.
* docs(dashboard): document enhanced Sessions, Skills hub, and Cron edit
Sessions: stats bar, rename, export, prune (+ screenshot). Skills: new Browse
hub view for search/install/update (+ screenshot). Cron: edit action. API
table updated with the new endpoints.
|
|||
| 8104b20269 | fix(xai): route video models by modality | |||
| 05022066ea | feat(bluebubbles): support group mention gating | |||
| c45593ceae |
docs: expand quickstart Skills section (#37047)
* fix(file_tools): block agent writes to ~/.hermes/config.yaml to prevent silent approval bypass * fix(approval): pair terminal-side gate for ~/.hermes/config.yaml writes Subway2023's #14639 blocks write_file/patch to ~/.hermes/config.yaml, but the terminal side was only partially paired: echo>/tee/cp/mv to config.yaml already tripped the project-config pattern, while `sed -i` and direct edits slipped through with auto-approve. An unpaired write_file deny is theater per SECURITY.md — the agent could flip approvals.mode=off via `sed -i` and the mtime-keyed config cache reloads it mid-session. config.yaml IS the security policy (approvals.mode/yolo/permanent allowlist live there), so it warrants real pairing, not a half-door. Add a _HERMES_CONFIG_PATH fragment mirroring _HERMES_ENV_PATH, fold it into _SENSITIVE_WRITE_TARGET (covers tee/>/>>/cp/mv), and add sed -i coverage for both config.yaml and .env. Pins 9 regression tests including no-regression guards (reads pass, /tmp writes pass). Co-authored-by: sbw2025 <subw3@mail2.sysu.edu.cn> * chore(release): map Subway2023 for PR #14639 salvage * docs: expand quickstart Skills section The Skills section was two bare commands with no framing — it never said what a skill is, how skills load, or what the install slug means. Expanded to explain the concept, the bundled catalog, install/browse/use flow, and slash-command activation. Removed the inaccurate /skills chat-command hint (skills become individual /<name> commands; hermes skills is the CLI verb). --------- Co-authored-by: sbw2025 <subw3@mail2.sysu.edu.cn> |
|||
| b571ec298d |
feat(dashboard): full administration panel — MCP, pairing, webhooks, credentials, memory, gateway, ops (#36704)
* feat(dashboard): backend API for MCP, pairing, webhooks, credential pool, memory, gateway lifecycle Adds REST endpoints so a remote admin can manage these without CLI access: - MCP servers: list/add/remove/test (config.yaml parity with hermes mcp) - Pairing: list/approve/revoke/clear-pending messaging codes - Webhooks: list/subscribe/remove (hot-reloaded JSON store) - Credential pool: list/add/remove rotation keys (via CredentialPool API) - Memory provider: status/select/disable/reset - Gateway lifecycle: start/stop (restart+update already existed) Secrets redacted on read; usable values only reach the agent at session start. All endpoints sit behind the existing dashboard auth gate. * feat(dashboard): backend API for ops + skills hub - Ops actions (spawned, log-tailed via /api/actions): doctor, security audit, backup, import, checkpoints prune - Ops reads (structured JSON): hooks list + allowlist status, checkpoints list with per-session size - Skills hub actions (spawned): install / uninstall / update - Registers new action log files for all spawn-based endpoints All gated by the existing dashboard auth middleware. * feat(dashboard): admin pages for MCP, pairing, webhooks, and system ops Adds four new dashboard pages + nav entries so a remote admin can manage Hermes without CLI access: - MCP: list/add/remove/test MCP servers - Webhooks: list/create/delete subscriptions (one-time secret reveal) - Pairing: approve/revoke/clear messaging pairing codes - System: gateway start/stop/restart, memory provider + reset, credential pool add/remove, ops (doctor/audit/backup/import/skills update) with a live action-log viewer, checkpoints prune, shell-hooks status api.ts: client methods + types for all new endpoints. App.tsx: routes + sidebar nav (plain labels, no i18n key required). Verified: tsc -b clean, production build succeeds, new pages lint clean, zero new eslint errors in App.tsx. * test(dashboard): cover admin API endpoints 20 tests across MCP, credential pool, memory, pairing, webhooks, ops, plus an auth-gate parametrize that asserts every admin endpoint requires the session token. Asserts request contract + CLI-config parity, not catalog values (per the no-change-detector-tests rule). * docs(dashboard): document MCP, Webhooks, Pairing, and System admin pages Adds Pages sections for the four new admin tabs and an Admin-endpoints table to the REST API reference. Updates the page description to reflect the dashboard's expanded role as a full administration panel. |
|||
| 2ed96372ad |
feat(skills): blank-slate skills — install --no-skills + opt-out/opt-in (#36228)
* feat(install): --no-skills flag for blank-slate default profile Add an install-time --no-skills flag so the default ~/.hermes profile can be created with zero bundled skills, matching what `hermes profile create --no-skills` already does for named profiles. The flag writes $HERMES_HOME/.no-bundled-skills and skips the install-time seed. sync_skills() now honors that marker with an early return (skipped_opt_out=True), so neither the installer, a later `hermes update`, nor a direct sync re-injects bundled skills into a profile that opted out. Previously the marker was only checked by seed_profile_skills() (named profiles); the default profile had no opt-out and `hermes update` would re-seed it every time. Tests: TestNoBundledSkillsOptOut covers marker-present (no-op) and marker-absent (normal seed) paths. * feat(skills): hermes skills opt-out / opt-in for existing profiles Adds an interactive counterpart to the install-time --no-skills flag so an already-installed profile (default or named) can toggle the .no-bundled-skills marker without reinstalling. - `hermes skills opt-out` writes the marker (stop future seeding). Safe by default: nothing on disk is touched. - `hermes skills opt-out --remove` ALSO deletes already-present bundled skills, but ONLY ones that are manifest-tracked AND byte-identical to their origin hash. User-edited bundled skills, hub-installed skills, and hand-written skills are never removed. Previews + confirms before deleting (--yes to skip). - `hermes skills opt-in [--sync]` removes the marker and optionally re-seeds immediately. Core logic lives in tools/skills_sync.py (set_bundled_skills_opt_out, is_bundled_skills_opt_out, remove_pristine_bundled_skills) reusing the existing manifest origin-hash machinery for the safety check. Tests: TestOptOutToggleAndRemove covers marker toggle idempotency and proves user-modified + non-bundled skills survive --remove. * docs: blank-slate skills — install --no-skills + opt-out/opt-in - features/skills.md: new 'Starting with a blank slate' section covering the install flag, profile-create flag, and runtime opt-out/opt-in, with a safe-by-default note. - reference/cli-commands.md: document the new skills opt-out / opt-in subcommands + examples. - reference/profile-commands.md: fix the marker filename (was .no-skills, actually .no-bundled-skills) and cross-link the runtime commands. Validated with a full docusaurus build (exit 0); the three edited pages compile clean with no new warnings. |
|||
| 51c68d4ab1 |
Add Hermes desktop app (#20059)
* feat: better composer etc * docs: add desktop and dashboard run instructions * fix(desktop): address security scan findings * fix(dashboard): resolve @nous-research/ui path under npm workspaces The sync-assets prebuild step shelled out to 'cp -r node_modules/@nous-research/ui/dist/fonts ...' with a path relative to apps/dashboard/. That works only when the dep is installed locally in the dashboard workspace, but 'npm install' at the repo root (the documented setup — see apps/desktop/README.md) hoists shared deps to the root node_modules under npm workspaces. The relative cp then fails with 'No such file or directory', sync-assets exits 1, the Vite build aborts, and 'hermes dashboard' surfaces a generic 'Web UI build failed' message. Replace the shell one-liner with scripts/sync-assets.cjs, which walks up from the dashboard directory looking for node_modules/ @nous-research/ui — working in both the hoisted (workspaces) and co-located (standalone) layouts. Also guards against a missing dist/fonts or dist/assets with a clearer error pointing at a rebuild of the UI package rather than silently copying nothing. * feat(desktop): support connecting to a remote Hermes backend Add HERMES_DESKTOP_REMOTE_URL and HERMES_DESKTOP_REMOTE_TOKEN env vars that, when set, short-circuit the local-child spawn in startHermes() and connect the Electron renderer to an already- running 'hermes dashboard' server reachable over the network. Motivating use case: WSL2 users who want to run the Hermes core (agent loop, tools, filesystem access) inside their WSL distribution while rendering the Electron GUI on native Windows. Before this change, the desktop app always spawned a local Python child on the same host as the renderer, which doesn't cross the WSL/Windows boundary. The remote path reuses waitForHermes() as a liveness probe (/api/status is in the backend's public endpoint allowlist), so the connection is only returned once the backend is actually ready. WebSocket URL derivation picks ws:// or wss:// based on the input scheme. URL validation rejects non-http(s) schemes and requires both env vars together to avoid a half-configured connection that would silently fall through to the spawn path. No behaviour change when the env vars are unset — the default local-spawn flow is untouched. Typical usage: # in WSL2 hermes dashboard --tui --no-open --host 0.0.0.0 --port 9119 --insecure # on Windows set HERMES_DESKTOP_REMOTE_URL=http://localhost:9119 set HERMES_DESKTOP_REMOTE_TOKEN=<session token> set HERMES_DESKTOP_IGNORE_EXISTING=1 (launch Hermes desktop) * ci(desktop): automate desktop releases Add GitHub Actions release channels for signed desktop installers and document the stable/nightly download paths. * feat: file tabs * refactor(desktop): tighten right-rail tab close API Promote closeRightRailTab/closeActiveRightRailTab as the single public entry point. Drops the activeTabRef + handleCloseDocument indirection in ChatPreviewRail, the unused $rightRailHasContent atom, and the legacy dismissFilePreviewTarget alias. -70 LOC. * feat(desktop): polish composer pill toward reference look Solid foreground-on-background send/voice-conversation circle (black-on-white in light, white-on-black in dark) anchors the right edge as the primary CTA instead of the orange theme primary. Bumps the primary control to 2.125rem so it visually outranks the ghost mic/plus controls. Opens up the surface padding (0.625rem x / 0.5rem y) so the input row breathes around its controls, and nudges the corner radius from 20 to 24px for a slightly pill-ier silhouette. LiquidGlass distortion is preserved. * feat(desktop): add startup and onboarding flow Add phase-based desktop boot progress, fresh-install sandbox testing, and first-run provider credential onboarding so packaged installs can start cleanly without manual settings detours. * fix(desktop): gate prompts on provider setup Show the desktop provider onboarding flow before prompt submission when no inference provider is configured, preventing fresh installs from falling through to backend credential errors. * fix(desktop): surface provider onboarding from session warnings Propagate credential warnings through session runtime info and open desktop onboarding whenever a session reports no usable provider, so unconfigured installs cannot fall through to prompt errors. * fix(desktop): route gateway provider errors to onboarding The "No inference provider configured" auth error reaches the renderer through gateway error events, not the prompt.submit promise; the previous patch only caught the latter, so the error toast still surfaced and onboarding never opened. Also strip credential-shaped env vars from the test:desktop:fresh sandbox so the packaged backend can't see provider keys leaking from the launching shell. * fix(desktop): use strict runtime check to drive onboarding setup.status returned True whenever any provider auth state was discoverable, including indirect fallbacks like a gh-CLI Copilot token. That made desktop think the user was set up while the agent's actual resolve_runtime_provider call still raised AuthError, leaving the user with a useless toast and no onboarding. Add a setup.runtime_check gateway method that runs the same resolver the agent uses on session creation, and switch the desktop onboarding overlay and prompt precheck to use it. * feat(desktop): OAuth-first onboarding using existing dashboard provider API Replace the engineer-flavored API key form with a Sign-in-first onboarding overlay that uses the dashboard's existing /api/providers/oauth catalog and PKCE/device-code endpoints (Anthropic, Nous, OpenAI Codex, etc.). API key entry is now a fallback tab with friendly provider names instead of env var prefixes, and the loud raw resolver error is gone in favor of a one-line welcome message. * fix(desktop): polish onboarding provider list Reorder OAuth providers so Nous Portal is first, give the segmented Sign in / API key control equal column widths, and replace the engineer-flavored backend names like "Anthropic (Claude API)" / "MiniMax (OAuth)" with friendlier in-app titles. External-CLI providers now show a softer subtitle and an external-link icon instead of a chevron. * refactor(desktop): split onboarding overlay into store + view Move the OAuth state machine, runtime check, copy-to-clipboard, and api-key save into store/onboarding.ts (matching the boot.ts pattern), leaving the overlay as a presentation layer that subscribes via useStore. Tabs are now table-driven, child panels read flow from the store instead of prop-drilling, and the polling/PKCE/error/success branches share a small Status atom. * fix(desktop): external CLI providers + center mode tabs External-CLI providers (Claude Code, Qwen Code) now open an in-overlay panel with the CLI command, copy button, and an "I've signed in" recheck instead of firing an invisible toast. Center the Sign in / API key tab control so it sits under the heading instead of hugging the left edge. * fix(desktop): drop onboarding tabs for an inline link, group device-code waiting state Replace the Sign in / API key tab pair with an "I have an API key" footer link under the OAuth provider list, with a "Back to sign in" affordance inside the API key form. Group the device-code "Waiting for you to authorize..." status next to the Cancel button so the alignment matches the action. * refactor(desktop): tighten onboarding store + overlay Drop the dead isOnboardingBusy/BUSY set, factor the catch-fallback dance into safeReq, and share a single reloadAndConnect helper between PKCE submit, device-code success, external recheck, and api-key save. In the overlay, extract Step / CodeBlock / FlowFooter / CancelBtn / DocsLink atoms so the four sign-in panels share the same chrome instead of repeating it inline. Net effect: fewer literal divs, one place to touch the spacing, and the code-block + footer rows are reusable across future flows. * fix(desktop): mount onboarding from frame 1 to kill the FOUT Default onboarding.configured to null (unknown until the runtime check resolves) and have the onboarding overlay render whenever it's not yet confirmed true. The boot overlay now yields to it, so the very first paint is the Welcome card with a "While we get you set up..." progress strip instead of a flash of the chat shell between boot dismiss and onboarding mount. The picker swaps in cleanly once the gateway opens and the runtime check confirms the user is not configured. Already-configured users see the same prep card briefly while their existing runtime warms up, then the overlay dismisses without touching the chat shell. * fix(desktop): top-align empty sessions placeholder The "Start a chat to build your history." empty state used a min-h-35 grid place-items-center container, which floated the text in a tall dead zone. Render it as a flat paragraph that sits right under the section header like the empty pinned state does. * refactor(desktop): drop dead boot overlay Onboarding overlay subsumes the boot card now that it mounts from frame 1 and renders boot progress inline. The standalone DesktopBootOverlay is unreachable in every flow (yields whenever onboarding has not confirmed configured, dismisses once it has). * fix(desktop): hide pinned/recents sections until first session A fresh sidebar showed the Pinned and Recent chats headers with floating empty-state copy underneath. Drop both sections (and the now-orphan SidebarEmptySessionState) when there are no sessions yet — they reappear after the first chat. Skeletons during initial load are unchanged. * feat(gui): route embedded TUI through dashboard gateway (#21979) Inject HERMES_TUI_GATEWAY_URL into dashboard PTY sessions so embedded ui-tui instances attach to the in-process websocket gateway, with coverage for the new env wiring. * Add desktop remote gateway settings Make the desktop gateway connection configurable from settings so local remains the default while remote backends can be saved, tested, and applied without environment variables. * feat(gui): first-class Messaging page + gateway menu redesign - Add Messaging page to the desktop app with per-platform setup, status, and inline guidance. Catalog derives from gateway.config Platform enum + plugin registry, so every messaging adapter the CLI supports (Telegram, Discord, Slack, Mattermost, Matrix, WhatsApp, Signal, BlueBubbles, Home Assistant, Email, SMS, DingTalk, Feishu, WeCom, Weixin, QQ, Yuanbao, API server, Webhooks, plugins) shows up without per-platform code. - New REST endpoints: GET /api/messaging/platforms, PUT and POST /test on the same path. Secrets go through the existing .env pipeline; enable/disable writes config.yaml. - Replace gateway statusbar dropdown with a richer panel: status row, icon-only restart + system-panel actions, recent activity (with timestamps trimmed in display, full text on hover), platform list. - Auto-poll the messaging page every 6s (paused when hidden) so status updates without a manual check. - Drop Settings / Command Center from the sidebar nav (still reachable via shortcuts and the titlebar cog). - Flatten top corners on Messaging/Skills/Artifacts/Chat panes. - Share new StatusDot component across messaging + gateway menu. - Fix gateway/config.py so an explicit platforms.<name>.enabled=false in config.yaml is honored when env tokens are present. - pb-9 on the chat content area for breathing room above the composer. * Potential fix for pull request finding 'CodeQL / Clear-text logging of sensitive information' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * pin electron version * hide application menu on non-mac systems * interpret compactPreview for non-string vlaues as JSON or an empty string * fix(desktop): keep composer contenteditable mounted across stacked toggle The composer rendered {input} inside two different parent fragments depending on `stacked`. When auto-expand flipped `stacked` (e.g. the moment typed text wrapped past two lines), React reconciled the two branches as different positions and unmounted/remounted the contenteditable. The fresh mount started empty, so any in-flight characters — most reliably reproduced by holding a key — were lost. Replace the conditional with a single CSS Grid whose template-areas swap on `stacked`. The three children (menu, input, controls) keep stable identities across the toggle; only their grid placement changes, which the browser handles without React tearing down the editor. * refactor(desktop): align install layout with install.ps1 / install.sh Make the desktop app's runtime layout match what scripts/install.ps1 and scripts/install.sh produce, so a desktop-only user and a CLI-only user end up with the same files in the same places and can share one install. Layout - ACTIVE_HERMES_ROOT = HERMES_HOME/hermes-agent (was: process.resourcesPath/hermes-agent, read-only) - VENV_ROOT = HERMES_HOME/hermes-agent/venv (was: userData/hermes-runtime) - desktop.log = HERMES_HOME/logs/desktop.log (was: userData/desktop.log) - HERMES_HOME default: %LOCALAPPDATA%\hermes on Windows, ~/.hermes elsewhere The packaged .app/.exe still ships a read-only payload at process.resourcesPath/hermes-agent (FACTORY_HERMES_ROOT). On first launch or after an installer-driven upgrade we sync factory -> active, then provision the venv and run pip install -e . against the active root. Key behaviors - Pin HERMES_HOME in the spawned Python's env so get_hermes_home() resolves to the same path resolveHermesHome() picked. Without this, Python falls back to ~/.hermes on every platform - fine on mac/linux, a split-state bug on Windows where our default is %LOCALAPPDATA%\hermes. - Detect developer installs by .git presence at ACTIVE; never overwrite a user's checkout via factory sync. - Marker at ACTIVE/.hermes-desktop-runtime.json (schema v4) tracks pyproject hash + factory version + runtime schema version. depsFresh fast-paths when nothing changed. - Dev (npm run dev) prefers SOURCE_REPO_ROOT over ACTIVE so devs run their local edits, not whatever's under HERMES_HOME. - Better error messages distinguish "no payload" from "no Python". - Preserve a legacy ~/.hermes on Windows when no %LOCALAPPDATA%\hermes exists, so users with prior pip/manual installs aren't orphaned. pyproject.toml - Promote fastapi, uvicorn[standard], ptyprocess (non-Windows), and pywinpty (Windows) to main dependencies. The dashboard backend (hermes dashboard) needs them at runtime; the previous lazy-import fallback was a footgun for fresh installs. - Empty the [pty] optional-extra; kept as a no-op back-compat alias for any existing pip install hermes-agent[pty] invocations. Drops the hardcoded BUNDLED_RUNTIME_REQUIREMENTS list in main.cjs - the desktop now installs whatever pyproject.toml says, single source of truth. Files - apps/desktop/electron/main.cjs: runtime layout, HERMES_HOME pin, factory->active sync, marker v4 - apps/desktop/scripts/test-desktop.mjs: track new venv location - apps/desktop/README.md: new Setup, Runtime Bootstrap, and Debugging sections - pyproject.toml: fastapi/uvicorn/pty backends in main dependencies; [pty] extra emptied Tested locally on Windows: npm run dev boots cleanly, sessions land at the new location, type-check + lint + test:desktop:platforms all pass. Verified end-to-end on a fresh Win11 VM via dist:win installer. Known gaps (filed as follow-ups, not in this PR): - Skills not seeded on packaged installs (sync_skills only runs in cmd_chat, not cmd_dashboard). Need to move to shared pre-dispatch. - Git Bash not bundled or detected; agent's terminal tool errors out with a useful message but desktop bootstrapper should pre-flight it. - install.ps1 / install.sh should be decomposed into composable phase libraries so the desktop bootstrapper can reuse them as a single source of truth across all install surfaces. * feat(desktop): theme polish, prose chat typography, composer chrome - DS tokens/midground, Backdrop, scoped scrollbars, typography plugin + prose - Composer liquid/radius utilities, thread font parity, tool/thinking cues - File tree label scale, preview flex, thread retry loading + streaming tests * feat(desktop): NSIS prereq detection page + auto-install via winget The packaged Windows installer now detects Python 3.11+ and Git for Windows at install time and offers to install missing prereqs via winget. Mirrors the prereq logic scripts/install.ps1 already runs for CLI installs, so desktop installer users get the same out-of-the-box experience as install.ps1 users. Why - Hermes' terminal tool calls bash.exe directly (tools/environments/ local.py); on Windows that's Git Bash from Git for Windows. Without it, the agent fails on the first terminal() call. - Hermes' Python runtime needs 3.11+. Without it, the desktop bootstrapper errors out at venv creation. - Both gaps surfaced on a fresh Windows 11 VM smoke test: VM had Python pre-installed but no Git, so the agent's first terminal call failed with "Git Bash isn't installed." - install.ps1 has had Install-Git + Install-Uv functions for ages. The desktop installer was the asymmetric outlier. How — NSIS prereq page - New file: apps/desktop/installer/prereq-check.nsh (plugged into electron-builder via build.nsis.include) - Real Wizard page using nsDialogs, inserted via customPageAfterChangeDir hook (between the Directory page and InstFiles). - Group boxes for Python and Git, each showing detection status. - Pre-checked install checkboxes when winget is available. - Auto-skips silently if both prereqs are already installed. - Falls back to manual download URLs when winget itself is missing. - Detection: - Python: probes `py -3.11`/`-3.12`/`-3.13`/`-3.14` via the Python launcher. Microsoft Store "Python stub" (no py.exe) is correctly classified as not-installed. - Git: `where git`. - winget: `where winget` (Win10 1809+ / Win11 with App Installer). - Install execution (in customInstall macro): - Python: nsExec::ExecToLog with `--scope user --silent`. Per-user install, no UAC prompt, output streams to install log. - Git: ExecShellWait via Windows ShellExecute. Critical because Git always installs per-machine and triggers UAC; ShellExecute preserves the foreground focus chain across non-elevated → elevated process spawns, so UAC actually comes to the foreground. nsExec::ExecToLog breaks the chain because winget runs hidden. - Both pass `--disable-interactivity --accept-package-agreements --accept-source-agreements` to suppress winget's own dialogs. - Verification: probes Git's standard install locations via FileExists rather than `where git`. NSIS's process inherits PATH at startup, so a freshly-installed Git won't be visible to `where` until restart. - Silent installs (/S) skip the prompts; managed deploys handle prereqs out-of-band via Group Policy / Intune. How — Electron-side safety net - New findGitBash() in main.cjs, parallel to findSystemPython(). Probes the same locations as tools/environments/local.py:_find_bash() so a positive result here means the agent's terminal tool will work. - ensureRuntime now throws a clear, actionable error on Windows when Git Bash isn't found, matching the existing "Python 3.11+ is required" error path. - Catches users the NSIS page doesn't: .msi installer users (NSIS prereq page doesn't run for MSI), `npm run dev` users, manual installers, anyone who unchecked the install boxes on the NSIS prereq page. - All gated on `IS_WINDOWS`; macOS / Linux unaffected. NSIS build issue (resolved) - electron-builder defaults to `-WX` (warnings as errors). NSIS optimizer emits "warning 6010: function not referenced" for our page functions because Page custom directives don't count as references in its static-analysis pass. The functions ARE called at runtime when NSIS invokes the page; the optimizer just can't see it statically. - Set `build.nsis.warningsAsErrors=false` in package.json so this spurious warning doesn't fail the build. (Documented option from electron-builder's nsisOptions.) Out of scope (filed for future work) - MSI prereq detection: Windows Installer custom actions are a different mechanism. Enterprise deploys typically handle prereqs via GP/Intune. - Bundle PortableGit + python-build-standalone in extraResources for zero-network installs. ~80MB increase. - Mac / Linux GUI prereq flows (different installer formats; Xcode CLT covers most macOS prereqs already; Linux is per-distro hard). Files - apps/desktop/installer/prereq-check.nsh (new, ~290 lines NSIS) - apps/desktop/package.json (build.nsis.include + warningsAsErrors) - apps/desktop/electron/main.cjs (findGitBash + preflight) - apps/desktop/README.md (Runtime prerequisites section) Cross-platform impact - macOS / Linux builds (dist:mac, dist:mac:dmg, dist:mac:zip): nsis config is ignored entirely; .nsh is dormant. - npm run dev: .nsh dormant; main.cjs preflight gated on IS_WINDOWS. - scripts/install.ps1, scripts/install.sh: no reference to any new files; CLI install paths untouched. - Hermes CLI / dashboard / gateway: no reference; runtime untouched. - All checks: node --check on main.cjs and test-desktop.mjs pass; npm run test:desktop:platforms 4/4 passing; node --test green. Tested - npm run dist:win produces signed .exe and .msi without errors. - Fresh Win11 VM (Python pre-installed, no Git): prereq page renders, Python check shows detected, Git checkbox pre-checked. Click Next → Git installs via winget with UAC prompt in foreground. - After install completes, Hermes launches and the agent's terminal tool can run bash commands. Verified Git Bash is detected at `C:\Program Files\Git\bin\bash.exe` by ensureRuntime's preflight. * feat: theme changes, composer tweaks, in app update ux, finesse * fix(cli): seed bundled skills on dashboard + gateway entrypoints `sync_skills(quiet=True)` was only being called from inside `cmd_chat`, which meant `hermes dashboard` (the desktop GUI's backend) and `hermes gateway` (Telegram/Discord/Slack/etc daemons) never seeded the bundled skill library into ~/.hermes/skills/. This surfaced as "No skills found" in the desktop GUI's skills panel on fresh installs, despite the agent having access to the full bundled library when invoked via `hermes chat`. scripts/install.ps1 worked around it by running skills_sync.py as part of Copy-ConfigTemplates, but that's not part of the desktop installer's bootstrap chain. Fix - Extract the skills-sync block from cmd_chat into a module-level `_sync_bundled_skills_quietly()` helper. - Call the helper from cmd_chat (preserving existing behavior), cmd_dashboard (after the --status/--stop early-return paths and fastapi import check, so we don't run skills_sync on management commands or when deps aren't installed), and cmd_gateway. Why these three entrypoints - cmd_chat: the user's primary CLI entrypoint - cmd_dashboard: the desktop GUI's backend; this is what `hermes dashboard --tui` invokes when the desktop bootstrapper spawns Hermes - cmd_gateway: long-running daemons where the user expects the agent to have full skill access Other entrypoints (cmd_config, cmd_doctor, cmd_login, cmd_status, etc.) are management commands that don't need skill discovery and were never running skills_sync in the first place — leaving them alone. Idempotence - tools/skills_sync.py is manifest-based: skipped skills cost milliseconds. Calling it from multiple entrypoints adds no real cost, and users running `hermes chat` then `hermes dashboard` get two fast no-ops on the second call. Failure handling - Helper wraps skills_sync in try/except. Skills are an enhancement, not a hard dependency — Hermes runs fine with an empty skills/ dir. Files - hermes_cli/main.py: + new helper `_sync_bundled_skills_quietly()` at module level + cmd_chat: replace inline block with helper call + cmd_dashboard: add helper call after fastapi import succeeds + cmd_gateway: add helper call before delegating to gateway_command * feat(desktop): hoisted todo widget, JSON tool summaries, history grouping & timer fixes - Hoist todo to first-class widget (shadcn checkboxes, brand colors, no tool-accordion). Header derives label from active task; non-active rows fade. - Replace raw JSON dumps with structured key/value summaries via formatToolResultSummary; nested error extraction for clearer failures. - Fix loaded-session grouping: stitch interleaved assistant/tool iterations into one bubble instead of orphaned synthetic messages. - Stable tool/thinking timers via keyed registry so unmount/scroll doesn't reset elapsed counts; gate "running" on real live thread state. - Reorganize chat-only assistant-ui components under components/chat/. * fix(desktop): address CodeQL alerts on PR #20059 - settings/helpers.ts: harden setNested against prototype pollution. POLLUTING_PATH_PARTS check is now applied at every assignment site (loop + leaf) and uses Object.defineProperty so CodeQL can see the guard inline rather than via a helper function call. - lib/markdown-preprocess.ts: rebuild the dangling-fence close regex from a fence-char + length instead of marker.replace(...). The marker is captured by `(`{3,}|~{3,})` so it can only be backticks or tildes, but CodeQL was tracing tainted input text into the RegExp source and flagging hostname dots from input as part of the pattern (false positive js/incomplete-hostname-regexp on the test fixture URLs). Reconstructing from a literal char breaks the dataflow. - scripts/notarize-artifact.cjs: drop args from the run() rejection message. Args carry --key-id / --issuer / key file path; the existing outer catch already squashes errors to a generic line, but CodeQL was flagging the args.join(' ') as clear-text logging of APPLE_API_KEY_ID. Composer DOM-text-as-HTML alerts (composer/index.tsx:379, :547) are already addressed in 4dd9732a9 — innerHTML assignment was replaced with renderComposerContents which builds DOM via replaceChildren / append text nodes (no HTML interpretation). * fix(desktop): inline prototype-pollution guard so CodeQL sees it CodeQL's dataflow doesn't follow the helper-function guard inside `safeSet`, so it kept flagging Object.defineProperty as prototype- polluting. Inline the literal `__proto__`/`constructor`/`prototype` check at the assignment site to break the dataflow. Behavior unchanged — same set of disallowed keys, same throw. * feat(ui-tui): resolve links to readable page titles Mirror desktop pretty-link behavior in the TUI by resolving HTTP links to page titles with shared caching and safe fetch filters, plus slug-based fallbacks so chat links stay readable even when title fetch fails. * fix(desktop): drop RegExp from dangling-fence close detection Previous attempt tried to break the dataflow by reconstructing the close-fence regex from a literal char + marker.length, but CodeQL still traced marker.length back to input and kept flagging the test-fixture URLs as hostname-regex sources (js/incomplete-hostname-regexp). Replace `new RegExp(...)` + `closeRe.test(body)` with a string-only hasCloseFenceLine() helper that splits on '\n' and uses ===. No regex on this path now, so input data can no longer reach a RegExp source. Behavior preserved: matches lines that are (whitespace + marker + whitespace), which is what the original `\n[ \t]*${marker}[ \t]*(?=\n|$)` matched. All 12 markdown-text tests still pass. * fix(process-registry): suppress windows-footgun false positive on guarded killpg Keep the existing POSIX-only process-group teardown path, but make the signal selection explicit via getattr and add an inline windows-footgun suppression marker on the guarded os.killpg line so the Windows footgun check no longer blocks CI on this intentionally platform-gated code. * feat(desktop): reconcile live tool events, polish thread chrome, harden boot - chat-messages: match tool rows by overlapping query/context/preview values so preview-first `tool.progress` rows reliably adopt later stable-id `tool.start` payloads instead of spawning ghost rows or mis-merging parallel same-name calls; preserve prior args/result across phases. - tui_gateway: emit full args + parsed result on `tool.start` / `tool.complete`, drop redundant `tool.started` re-emit from `tool.progress`. - electron/main: prefer SOURCE_REPO_ROOT before PATH `hermes` in dev so local backend edits actually run; split hardening helpers into `electron/hardening.cjs` with tests. - thread/tool UI: one-shot enter animation keyed by stable ids, braille spinner for running rows, Cursor-like disclosure rows, drill-down + duration/count formatting via new tool-fallback-model. - composer: extract `text-utils`, drop liquid-glass overrides. - right-rail: split preview-pane into preview-console / preview-file. - runtime: incremental external-store runtime + runtime-readiness gate; onboarding store + tests; route-resume hook test. - regression tests for live tool reconciliation (parallel tools, id-less progress, preview-first rows, structured args/results). * feat(desktop): add ripgrep to NSIS prereq page + polish layout Add ripgrep as a third (recommended) prereq alongside Python and Git in the NSIS prereq detection page, and clean up the page layout based on on-VM testing. Why ripgrep - Hermes' search_files tool calls `rg` directly for content + filename search (tools/file_operations.py:1382). Falls back to grep/find from Git Bash when missing — works but slower and noisier (no .gitignore awareness). - ~5MB winget install via `BurntSushi.ripgrep.MSVC --scope user` — no UAC prompt, parallel to how Python installs. - scripts/install.ps1 already installs ripgrep as part of Install-SystemPackages; this brings the desktop installer to parity. Why "recommended" not "required" - Python and Git are hard requirements: without them the agent runtime or terminal tool refuses to start. The bootstrapper preflight throws. - ripgrep is a performance enhancement: missing it just means slower searches. Page wording reflects this; failure to install is logged but doesn't show a MessageBox or block. Layout polish (response to on-VM screenshot review) - Wizard header now correctly reads "System Requirements" instead of the leftover "Choose Install Location" from the previous page. Set via `GetDlgItem $HWNDPARENT 1037/1038` + WM_SETTEXT — the standard NSIS pattern for overriding the page header on a custom Page. - Removed redundant in-body title + verbose intro paragraph; the wizard header IS the title now. Body has one short intro line. - Group boxes tightened to 26u with content positioned just below the groupbox title (not top-anchored status + bottom-anchored checkbox with empty space in the middle). All three panels + footer fit comfortably in 126u, well under the 140u page limit. - Checkbox labels simplified: dropped "(per-user, no admin prompt)" and "(administrator approval required)" suffixes. The footer note still calls out UAC for Git when relevant. - Footer text trimmed to fit cleanly without clipping. Install order (in customInstall macro) - Python → ripgrep → Git - Python and ripgrep are silent and run first; Git's UAC prompt comes last so the user's approval interaction isn't interrupted by silent activity afterwards. Skip behavior unchanged - All three detected → page auto-skips via Abort - Silent install (/S) → customInstall winget block skips - User unchecks all → page advances without running winget Files - apps/desktop/installer/prereq-check.nsh: ripgrep detection block, ripgrep page panel + checkbox, ripgrep customInstall block, GetDlgItem header override, layout reflow - apps/desktop/README.md: Runtime prerequisites section updated to list ripgrep as recommended, with manual winget command * feat(desktop): add model-confirmation step to onboarding After OAuth/API-key login completes, onboarding now shows a confirmation card with the curated default model and a Change button before dropping the user into chat. Closes the gap where the desktop's `model.default` was empty after first launch and the agent had to fall back to whatever heuristic happened to fire — leaving users wondering "why am I getting sonnet-4 when I logged into Nous Portal?" Why - Desktop onboarding only persisted credentials, never `model.default`. The CLI's `hermes model` command pairs provider + model selection, but the desktop's onboarding skipped the model step entirely. - Result: users saw whichever model the agent's auto-fallback picked, unpredictably and undocumented. - For the BUILD demo we want users to land on the model they expect for their provider, with a clear "this is what you're getting" UI and a one-click path to change it before chatting. How - New `confirming_model` flow status carries the just-authenticated provider slug, current default model, label, and a saving flag. - `completeWithModelConfirm()` runs after credentials succeed: reloads env, verifies runtime, fetches /api/model/options to find the curated first-model for the provider, persists it via /api/model/set, then transitions into `confirming_model`. - If anything fails (no providers returned, network error), falls through to the previous behaviour — onboarding completes without the confirm step. Polish, not a hard requirement. - All four credential paths (device_code OAuth, PKCE OAuth, external CLI flow, API key) now use completeWithModelConfirm instead of reloadAndConnect. UI - `ConfirmingModelPanel` shows: green "<provider> connected" banner, card with "Default model: <name>" + Change button, and a "Start chatting" CTA that finalises onboarding. - Reuses the existing `ModelPickerDialog` (the same picker available from the chat shell) for the change-model UX. Search, filtering, multi-provider listing — all already built. - Stacking: ModelPickerDialog defaults to z-130, which renders UNDER the onboarding overlay (z-1300) and breaks pointer events. Added optional `contentClassName` prop to ModelPickerDialog so callers can override; onboarding passes `z-[1310]`. Provider-slug matching - For OAuth flows: pass `provider.id` directly as the preferred slug. - For API-key flows: `OPENROUTER_API_KEY` → "openrouter" via env-key prefix strip. Also includes the user-visible label as a fallback candidate. - fetchProviderDefaultModel falls back to the first authenticated provider in the response if no preferred slug matches — so even a miss still surfaces a reasonable default. Files - apps/desktop/src/store/onboarding.ts: + new `confirming_model` flow variant + fetchProviderDefaultModel + completeWithModelConfirm helpers + setOnboardingModel (optimistic update + revert on failure) + confirmOnboardingModel (finalises onboarding from the card) - reloadAndConnect (replaced; the four call sites now go through completeWithModelConfirm) - apps/desktop/src/components/desktop-onboarding-overlay.tsx: + ConfirmingModelPanel component + new branch in FlowPanel for status `confirming_model` + ModelPickerDialog usage with z-[1310] content class - apps/desktop/src/components/model-picker.tsx: + optional `contentClassName` prop on ModelPickerDialog so the dialog can be stacked on top of other fixed overlays Tested - `npm run type-check` passes - `npx eslint` clean on touched files - Live test in `npm run dev`: cleared onboarding cache, walked through Nous device-code flow, saw confirm card with curated default, clicked Change → ModelPickerDialog rendered above the onboarding overlay with working pointer events, picked a different model, "Start chatting" persisted to ~/.hermes/config.yaml. * fix(desktop): suppress generic provider warning in onboarding Hide the red setup notice when the message is the generic missing-provider guidance, since onboarding already presents provider auth actions. Centralize provider-setup matching across desktop hooks and add coverage for the matcher. * fix(desktop): add 2u clearance below prereq checkboxes Group box bottom border was clipping the checkboxes by 1-2px. Bumped each box height 26u→30u; checkboxes now sit 2u above the bottom border. * fix(nix): refresh dashboard lockfile hash Update the web npm deps hash in nix/web.nix to match the committed apps/dashboard/package-lock.json so bb/gui passes the nix lockfile check. * fix(desktop): install TUI deps in release workflow Ensure desktop release builds install the standalone ui-tui package before bundling the TUI payload. * fix(desktop): run release builder from app package Invoke the desktop builder through the package script so electron-builder uses apps/desktop/package.json. * fix(desktop): expand release artifact names safely Build desktop artifact names from workflow version/channel while preserving electron-builder platform macros. * fix(desktop): use package artifact naming in release workflow Let electron-builder's desktop package config provide platform-specific artifact extensions while the workflow injects the release version/channel metadata. * fix(nix): fetch dashboard npm deps from package root Point the dashboard npm dependency fetch at apps/dashboard so Nix can find the package lockfile after the dashboard move. * fix(nix): build dashboard from package directory Set the web package source root to apps/dashboard so npm patch/build phases run beside the dashboard lockfile while keeping apps/shared available as a sibling. * feat(desktop): render LaTeX math via KaTeX after streaming completes Add @streamdown/math plugin to the chat markdown renderer. Inline ($x^2$) and block ($$...$$) math both supported with singleDollarTextMath enabled. Plugin is gated to non-streaming state to match the existing pattern for syntax highlighting — math renders when the message completes, avoiding KaTeX re-render churn during streaming. KaTeX CSS is imported in styles.css; ~30KB CSS + ~430KB JS added to the bundle. Smoothness improvements during streaming deferred to a follow-up. * perf(desktop): memoize KaTeX renders so math streams without re-rendering Wrap rehype-katex with a per-equation LRU cache (keyed by displayMode + source text) and re-enable math during streaming. Stock @streamdown/math runs rehype-katex on every markdown commit, so each new token re-katexes every equation in the message. For math-heavy responses (an equation derived step-by-step) that's hundreds of ms of wasted work per token and the streaming UI chokes. With memoization, each equation pays katex.renderToString exactly once; subsequent tokens re-walk the tree but hit cache for unchanged equations. The wrapper mirrors rehype-katex's semantics exactly: same class detection (language-math, math-inline, math-display), same <pre>-walk-up for fenced math blocks, same parent.children.splice replacement, same SKIP traversal, same strict-then-lenient render strategy with VFile message reporting. Cached children are structuredCloned on each splice so downstream rehype plugins or toJsxRuntime can't mutate the cache. * fix(desktop): declare katex-memo deps directly + drop per-app lockfile katex-memo.ts (added in 112cad59b) imports hast-util-from-html-isomorphic, hast-util-to-text, remark-math, katex, and unist-util-visit-parents but those were never added to apps/desktop/package.json. They were silently resolving via @streamdown/math at the workspace root, which broke the moment `npm i --prefix apps/desktop` ran with the per-workspace lockfile because that install only consults apps/desktop/package.json. Add them as direct deps, plus unified/vfile/@types/hast for the type imports. Also delete apps/desktop/package-lock.json — root package.json declares workspaces: ["apps/*"], so npm manages all lockfile state at the root. The stale per-app lockfile is what made `npm i --prefix apps/desktop` diverge from the workspace install in the first place and left an empty apps/desktop/node_modules/@assistant-ui/ stub that Vite's dep optimizer then tried (and failed) to open at @assistant-ui/core/dist/internal.js. * feat(desktop): disable Backdrop noise overlay by default The noise overlay defaulted to on, which adds a busy speckle layer over the whole window for every new user. Flip the Leva default to off; the toggle stays in Backdrop / Noise for anyone who wants it back. * fix(desktop): polish LaTeX rendering — currency, code blocks, brackets Five distinct bugs surfaced from a math-heavy stress test: 1. Adjacent code fences glued together. scrubBacktickNoise's second-pass regex /``\s*``/g matched the LAST 2 backticks of one fence + whitespace + FIRST 2 backticks of the next, collapsing two blocks into one. Fixed with lookbehind/lookahead so we only match exactly 2 backticks not part of a longer run. 2. Whitespace eaten between fences and following content. stripPreviewTargets internally calls .trim() which strips leading/ trailing whitespace from each split-segment. For segments between two fences this collapsed \n\n to '', gluing fence close to next block. Fixed by capturing leading/trailing whitespace at the call site and restoring it after the transform. 3. Currency dollar signs eaten as math. With singleDollarTextMath:true remark-math greedy-matched any pair of $, so '$5 ... $10' became one inline math span. Added escapeCurrencyDollars to escape $<digit> patterns to \$<digit> in prose segments (not in code). Trade-off: math expressions starting with a digit (rare — '$5x = 10$') get escaped too. Mirrors the convention in ChatGPT/Claude's UIs. 4. \(...\) and \[...\] LaTeX brackets unsupported. Models often emit these instead of $...$ / $$...$$. Added rewriteLatexBracketDelimiters preprocessor pass. 5. ```latex / ```tex blocks were being routed to KaTeX via a rewrite to ```math. Aligns with GitHub markdown convention: ```math = render as math; ```latex / ```tex = LaTeX/TeX source code (syntax highlighted, not rendered). Conflating them broke teaching/showing-source use cases. MATH_FENCE_LANGUAGES pruned to {'math'} only. Also flipped parseIncompleteMarkdown to true (was !isStreaming) so the math parser can't see $ inside streaming-but-not-yet-closed code fences. Shiki was already deferred via defer={isStreaming} so this doesn't introduce new tokenization cost. Test: 18/18 existing tests still pass; one test updated to expect escaped \$ in currency-prose-with-URL case. * fix(desktop): detect Python via registry/filesystem; pin to 3.11–3.13 Two related fixes for Python detection on Windows: 1. py.exe (Python launcher) is missing from per-user installs that didn't check the launcher option, so 'py -3.X --version' alone misses real Python installs. User-reported case: clean Win11 + official Python.org 3.14 install -> 'where py' returned nothing, our installer offered to install Python again. Both NSIS prereq page and main.cjs now probe in this order: 1. py.exe launcher (when present) 2. PEP 514 registry: HKLM/HKCU\SOFTWARE\Python\PythonCore\<v>\InstallPath 3. Filesystem: %ProgramFiles%\Python<v>, %LocalAppData%\Programs\Python\Python<v> Crucially, we never fall back to running 'python.exe' from PATH on Windows — the WindowsApps stub at %LOCALAPPDATA%\Microsoft\ WindowsApps\python.exe is a redirector that opens the Microsoft Store window if no Store Python is installed. Triggering that during boot would be terrible UX. Registry/filesystem probes never execute the binary. 2. Drop 3.14 from the supported version set. Several Hermes deps (notably pywinpty, which carries Rust crates like windows_x86_64_msvc) don't yet publish 3.14 wheels. With wheels missing, 'pip install -e .' falls back to building from sdist, which needs a Rust toolchain — users see 'could not compile windows_x86_64_msvc build script' on first run. install.ps1 sidesteps this by pinning to 3.11 via uv; the desktop installer doesn't yet have the same uv-managed-Python pathway, so for now we accept 3.11/3.12/3.13 and tell winget to install 3.11 if none of those are present. Revisit when the wheel ecosystem catches up to 3.14 (~early 2026). * feat(desktop): Cron, Profiles, usage analytics, and titlebar fixes - Add Cron and Profiles sidebar routes with full CRUD-style flows and API wiring. - Extend Command Center with auxiliary task overrides and a Usage panel (7d/30d/90d). - Fix titlebar geometry for WSL/Windows (native overlay width, tool spacing). - Remove stray merge conflict markers from pyproject.toml optional deps. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(title-bar): position sidebar toggle button * feat(desktop): composer queue — queue many, edit/delete/cancel-edit, Cursor-style Press Enter while busy with a draft to queue it; with no draft to interrupt and send the next queued turn. Auto-drains one queued turn each time the session settles, same as Cursor. Queue persists across reloads so an interrupted-and-queued turn isn't lost on refresh. Each queued row supports edit-in-composer (with explicit Save/Cancel), send-now (↑), and delete. Drain skips only the entry currently being edited so the rest of the queue keeps flowing. Queue dequeue is transactional — an entry only leaves the queue after `prompt.submit` is accepted, so a rejected submit doesn't drop the turn. Also shrinks the `[interrupted]` marker to a muted one-liner and drops its assistant footer so it stops looking like a real reply. * fix(desktop): handle empty usage analytics totals Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address PR review titlebar and usage races Co-authored-by: Cursor <cursoragent@cursor.com> * feat(desktop): add MCP settings and live subagent tree Surface configured MCP servers in Settings with JSON edit/save and a gateway-backed reload action so users can manage tool servers without falling back to slash commands. Track live subagent gateway events in a desktop store, show active subagent counts in the Agents statusbar item, and replace the Agents overlay stub with a live spawn tree for the active session. * fix(desktop): move power-user views out of sidebar Keep Cron and Profiles available through lower-prominence chrome entry points so the workspace sidebar stays focused on core chat navigation. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(desktop): subagent overlay reads like a live transcript, not a dashboard Strip the card chrome and rewire /agents to feel like peeking into the child agent's stream: - subagents store: single `stream` of typed entries (thinking/tool/progress/ summary) replaces the parallel notes/thinking/tools arrays. Drop unused fields (toolsets, depth, apiCalls, reasoningTokens, sessionId). - agents view: no OverlayCards, no boxed stream, no per-row borders. Goal + status pill + indented stream lines, full row width. - Group root spawns into "Delegation N" sections when batch shape + spawn time match — hides task-index interleaving and makes hierarchy obvious. - Sort tree by spawn time, then task_index. Step indicator is one colored pill (primary while running, emerald when done) inside the row, not a trailing pill that wrapped under the chevron. - Tree picks up `subagent.start` (not only `spawn_requested`) and prunes delegate-tool fallback rows once native subagent events land for the session — fixes duplicate "Delegated task" rows alongside the real ones. * feat(desktop): Esc closes every OverlayView-based overlay Lift the keyboard handler into the shared OverlayView so Agents, Settings, Command Center — and anything we build on top of it later — all dismiss on Esc by default. Nested Radix dialogs stop propagation themselves, so a modal opened inside an overlay (e.g. model picker inside Settings) still closes the modal first, not the overlay underneath. Drop the now-redundant Esc handlers in Settings (kept Cmd/Ctrl+P) and Command Center. * fix(desktop): drop numbered step pill on subagent rows The pill was getting clipped at the overlay edge anyway. Just use the status glyph (●/✓/✗/■/○) — the delegation header already conveys "3 workers, 3 active", and order in the list implies which step you're looking at. * fix(desktop): drop noisy "returned N items / empty object" stub strings When a tool returns nothing useful, the row should be silent — the title ("Search Files", etc.) already tells the user what happened. Counting the fields in an opaque payload is engineer-noise. `formatToolResultSummary` and `minimalValueSummary` now return '' for empty arrays / records / unrecognized values; tool-fallback already hides the detail section when its body is empty. * refactor(desktop): subagent rows borrow chat tool patterns (fade-in, lucide glyphs, shimmer) Pull the agents view closer to how chat tool blocks render: - statusGlyph() returns the same lucide BrailleSpinner / CheckCircle2 / AlertCircle vocabulary as tool-fallback's statusGlyph - Stream lines fade-in via useEnterAnimation (one-shot WAAPI), keyed per entry so streamed deltas settle in instead of popping - Subagent rows fade in too, and pick up the existing data-slot=tool-block spacing rules between blocks - Active stream line trails a BrailleSpinner instead of a hand-rolled pulsing rectangle - Goal text drops FadeText (which forces nowrap); keep FadeText only for the single-line meta subtitle - Running rows shimmer the title — same affordance the chat thinking row uses * refactor(desktop): make /agents subagent-only, drop sidebar + dead sections Activity rail and History stub were both noise. Strip the split layout, sidebar, route enum, and the rail/stub helpers — the overlay is now just the spawn tree, centered in a max-w-3xl column so it stops claiming the whole screen for one section's worth of content. * feat: update cron modals * Add dedicated GUI log stream for dashboard debugging. Capture dashboard and PTY websocket lifecycle failures in gui.log and expose it via hermes logs. * Improve desktop runtime UX by surfacing inference readiness in gateway status and hardening WSL link opening. This also stabilizes markdown code/table block spacing and adds root-install guards so desktop dev runs use a healthy workspace dependency tree. * Log detailed GUI websocket failure metadata. Capture richer reject/disconnect/send/parse context for dashboard gateway websocket flows so GUI connection failures are diagnosable from logs. * Default dashboard startup logging to GUI mode. Detect the dashboard subcommand during early CLI bootstrap so gui.log is attached from process start and GUI startup failures are always captured. * Clean up gateway status conditionals and logging bootstrap mode detection. Simplify nested dashboard gateway status branches for readability and use a concise first-subcommand check when selecting early GUI logging mode. * add logging to nsis installer * feat: glass ui pass * fix(desktop): persist inline assistant errors across hydrate/resume - Detect provider failure text arriving via message.complete (HTTP 4xx, "API call failed after N retries", Provider/Gateway error: ...) and persist as an inline assistant error instead of regular completion text, blocking the hydrate that was wiping it. - preserveLocalAssistantErrors: merge by id so same-id hydrated messages keep their local error, and preserve the optimistic user+error pair as a unit (with tail-user dedupe). - Hook all hydrate/resume writers (use-session-actions resume + fallback, hydrateFromStoredSession, syncSessionStateToView) into the merge so stale snapshots can't clobber a failed turn. - Add error to chatMessagesEquivalent so the resume diff actually sees error-only changes and paints them. - editMessage on a failed turn now submits a plain resend (no truncate_before_user_ordinal) and retries plainly on the "no longer in session history" race. Style polish on touched files: - Inline error: text-only treatment (no card). - User stop / edit-composer send: shared Tabler IconPlayerStopFilled glyph + shared icon-button class slot for parity. * feat(desktop): theme xterm with active light/dark mode The right-sidebar terminal hardcoded a light palette, which read poorly on the dark glass surface. Subscribe to `useTheme().resolvedMode` and hot-swap `term.options.theme` so Shift+X (and any other mode change) updates the terminal in place without tearing down the PTY session. Dark mode uses xterm's built-in defaults (white fg/cursor + vivid ANSI 16) with just a transparent background so the glass shows through; light mode keeps the existing hand-tuned overrides for legibility on a bright surface. * feat(sidebar): right-click + drag-reorder sessions and workspaces - Wire right-click on session rows to open the same actions menu; suppresses the OS-native context menu so Windows stops looking awful. - Share dropdown + context menu items via useSessionActions() driving a single declarative ItemSpec[]; render polymorphic over MenuItem. - New shadcn ContextMenu primitive mirroring DropdownMenu styling. - Restore drag-and-drop reordering for Agents (lost during the cwd cleanup) and add reordering of workspace groups via a right-side grab handle. Pinned reorder unchanged. - Generic orderByIds<T> replaces the duplicated session/group orderers; useSortableBindings() hook collapses the two Sortable wrappers. - cursor-pointer on every actionable element; cursor-grab on handles. - KISS pass: baseName() helper, AGE_TICKS table, single WORKSPACE_PAGE constant, flatter SidebarSessionsSection render. * feat(desktop): solarize the xterm palette in both light & dark xterm's default ANSI 16 is tuned for dark and reads candy-bright on the light glass surface (vivid cyans/greens). Ship the canonical Solarized palette (Schoonover) for both modes — same 16 accents either way, only fg/cursor swap between `base00/01` (light) and `base0/1` (dark), so a prompt's colors look uniform across a Shift+X toggle. Background stays transparent in both modes — Solarized's cream/slate backgrounds would fight the glass. * feat(desktop): virtualize chat thread + sidebar via TanStack Virtual Replaces `use-stick-to-bottom` and per-row session rendering with `@tanstack/react-virtual`, matching what Cursor uses. Chat thread (`thread-virtualizer.tsx`): - Natural-flow virtualization (padding spacers, not absolute items) so `position: sticky` on the human bubble still resolves cleanly against the scroller. - Custom at-bottom anchor: pins when armed, disarms on user-driven upward scroll, re-arms at bottom, jumps on session switch + `thread.runStart`. - Loading indicator and `--thread-last-message-clearance` move to a real `[data-slot=aui_composer-clearance]` node; drops the brittle `:nth-last-child(1 of …)` rule that can't fire reliably under virtualization. Sidebar (`virtual-session-list.tsx`): - Flat agents list virtualizes at >=25 rows; pinned and workspace-grouped paths stay direct-render. - `SortableContext` keeps all IDs; only the window mounts; dnd-kit's `setNodeRef` is merged with `virtualizer.measureElement` so rows participate in both DnD hit-testing and TanStack measurement. Drops `use-stick-to-bottom`. Streaming test gets a global `offsetWidth/offsetHeight` stub so the virtualizer's viewport sizing works in jsdom; the scroll-up-doesn't-pull-back invariant still passes. * feat: more ui qa * fix(desktop): trim sidebar terminal startup spacer Drop zsh's initial spacer row before writing the first terminal prompt so new sidebar terminal sessions do not open with a selectable blank line. * chore: uptick * feat(desktop): thin installer + first-launch install.ps1 bootstrap Converges the Windows packaged desktop installer onto a single canonical install topology: drop the Electron shell only (~80MB instead of ~500MB), clone Hermes Agent at a build-time-pinned commit on first launch via install.ps1's stage protocol, and treat the resulting git checkout at %LOCALAPPDATA%\hermes\hermes-agent\ as the canonical install location (same path the CLI installer uses). Future updates flow through the existing applyUpdates() git-pull path. Replaces the previous fat-installer architecture where the .exe bundled a pre-staged hermes-agent source tree under resources/hermes-agent/ that was then sync'd into ACTIVE_HERMES_ROOT at launch -- a complicated factory-vs-active dance with several footguns (FACTORY_HERMES_ROOT mismatch on path resolve, isGitCheckout guard regressions, pyproject hash drift detection inside the sync loop). Architecture overview --------------------- Build time apps/desktop/scripts/write-build-stamp.cjs writes apps/desktop/build/install-stamp.json with {commit, branch, builtAt, dirty}. Honours $GITHUB_SHA / $GITHUB_REF_NAME in CI, falls back to `git rev-parse HEAD` locally. apps/desktop/scripts/stage-native-deps.cjs copies the runtime subset of @homebridge/node-pty-prebuilt-multiarch from the workspace-root node_modules into apps/desktop/build/native-deps/. Workspace dedup hoists this dep to the root, out of reach of electron-builder's `files:`-restricted collector; staging gives us a deterministic path to extraResources. electron-builder ships both into resources/install-stamp.json and resources/native-deps/ respectively. Boot resolver (electron/main.cjs) Resolver order: 1. HERMES_DESKTOP_HERMES_ROOT override 2. SOURCE_REPO_ROOT (dev mode) 3. ACTIVE_HERMES_ROOT git checkout WITH .hermes-bootstrap-complete marker -- the post-install fast path 4. `hermes` on PATH (CLI-installed user adding the desktop) 5. pip-installed hermes_cli via system Python 6. bootstrap-needed sentinel -> hand off to runBootstrap Deletes the entire FACTORY_HERMES_ROOT / RUNTIME_MARKER / syncTreeExcludingVenv machinery (-200 lines). The isGitCheckout guard that bit us in the install.ps1 PR is gone. First-launch bootstrap (electron/bootstrap-runner.cjs) 1. Resolve install.ps1: prefer SOURCE_REPO_ROOT/scripts (dev), else download from GitHub raw at INSTALL_STAMP.commit (cached at HERMES_HOME\bootstrap-cache\install-<sha>.ps1). 2. Fetch the stage manifest via install.ps1 -Manifest -Commit X -Branch Y. 3. Iterate stages: install.ps1 -Stage <name> -NonInteractive -Json -Commit X -Branch Y per stage. 4. On all stages green: write the .hermes-bootstrap-complete marker with {schemaVersion, pinnedCommit, pinnedBranch, completedAt, desktopVersion}. Per-run log to HERMES_HOME\logs\bootstrap-<ts>.log. Cancellation via AbortSignal. Manifest cache so retries don't re-download. Install overlay (src/components/desktop-install-overlay.tsx) Mounted alongside the existing onboarding overlay; flexbox card with header (static) + middle (scrollable) + footer (failure-only, static). Subscribes to hermes:bootstrap:event IPC + resyncs from hermes:bootstrap:get on mount/reload. Renders: - 14-stage checklist with per-stage state icons - Overall progress bar + current-stage spotlight - Auto-expanded installer-output panel on failure - "Copy output" button (full ring buffer + error to clipboard) - "Reload and retry" wired through hermes:bootstrap:reset to clear main.cjs's latched failure Synthetic empty-manifest event from main.cjs flips the overlay to 'active' immediately so the slow install.ps1 download doesn't leave the user staring at the generic Preparing splash. Failure latching (main.cjs) bootstrapFailure module-scope variable holds the rejection after install.ps1 fails. startHermes() throws the latched error immediately when set, bypassing the entire ensureRuntime + runBootstrap chain. Without this, the renderer's ensureGatewayOpen retries would re-run install.ps1 in a 5-10 min hot loop while the user was still reading the failure overlay. Cleared via hermes:bootstrap:reset on user-driven retry. Unsupported-platform overlay (1F) macOS / Linux packaged builds (no install.sh stage protocol yet) emit an unsupported-platform event with a copy-pasteable install command + docs URL. Dedicated overlay branch with "Copy command" + "I've run it -- retry" buttons. install.ps1 additions (Phase 1F.3 + 1F.5) ----------------------------------------- New -Commit and -Tag string params. Precedence Commit > Tag > Branch. Honoured by all three code paths (update / fresh clone / ZIP fallback), with archive URL selection that handles each ref-type variant. Detached-HEAD checkouts intentionally -- they're pins, not branches the user pulls into. EAP=Continue wrap around the new pin-step git invocations. `git fetch origin <commit>` writes the routine 'From <url>' info line to stderr; under the script's global EAP=Stop that terminates the script even though fetch+checkout succeed. Matches the established pattern in Install-Uv, Test-Python, _Run-NpmInstall. Backend fix (hermes_cli/web_server.py) -------------------------------------- CORS allow_origin_regex now accepts Origin: 'null'. Packaged Electron loads index.html via file://; Chromium sets the WebSocket upgrade Origin header to the opaque origin 'null', which the old regex rejected with HTTP 403 before gateway_ws() ever ran. This failure mode was masked in the older FACTORY_HERMES_ROOT architecture because the resolver often found an existing hermes on PATH with different binding behavior. Security maintained: localhost-only bind keeps cross-machine pages out; per-process session token still gates every authenticated /api/ endpoint regardless of Origin. Desktop QoL ----------- DevTools is now enabled in packaged builds (F12 / Cmd+Opt+I). Field-debugging trade-off: tiny attack surface increase versus a much better support story when CSP / WS / theme issues surface. NSIS prereq-check page deleted (-767 lines). The standard Welcome -> License -> Directory -> InstallFiles -> Finish wizard now installs without custom Python/Git/ripgrep detection -- those prereqs are install.ps1's job at first launch. Test infrastructure (Phase 1G) ------------------------------ apps/desktop/scripts/test-desktop.mjs rewritten as a cross-platform bundle validator (was darwin-only and asserted on dead factory- payload paths): NEGATIVE: hermes_cli/main.py is NOT shipped (regression guard) POSITIVE: install-stamp.json carries a real commit + branch POSITIVE: node-pty native deps shipped under resources/native-deps POSITIVE: renderer dist/index.html reachable (asar or unpacked) New nsis mode and npm run test:desktop:nsis script. Validated end-to-end on clean Win10 VM -------------------------------------- Confirmed: NSIS installer drops Electron shell, app launches, install overlay shows progress, install.ps1 clones the pinned commit, 14 stages run to completion, marker written, backend spawns, WebSocket connects, onboarding overlay asks for API key, main UI loads, integrated terminal works. Failures handled: bootstrap stays failed (no hot-loop retry), "Copy output" gives actionable transcript, "Reload and retry" explicitly re-runs install.ps1. What's deferred --------------- - MSIX wrapping (Phase 2): same Electron .exe under MSIX manifest with runFullTrust, signed and submitted to Microsoft Store. - install.sh stage protocol parity (Phase 2): once shipped, the unsupported-platform overlay becomes drive-it-yourself and macOS/Linux packaged installers gain feature parity with Windows. * feat(desktop): persistent terminal pane + fullscreen takeover Adds a VSCode-style "focus terminal" toggle to the right sidebar's Terminal tab that takes over the chat pane area without unmounting the shell. The xterm host is mounted once at the layout root and CSS-overlayed onto whichever <TerminalSlot /> is currently active, so the PTY session, scrollback, selection, focus, and WebGL renderer survive every toggle. Also: - WebGL renderer (matching dashboard ChatPage) so Hermes' TUI skins paint faithfully instead of muting through xterm's default DOM renderer - File drag/drop from the project tree or OS into xterm — paths are shell-quoted (zsh/bash/pwsh/cmd) and written straight into the PTY - Solarized dark canvas with brights promoted to real accent variants (Schoonover's UI-gray brights washed out every TUI accent) - Strip NO_COLOR/FORCE_COLOR/COLORFGBG/TERM=dumb leaking from non-tty parents (CI runners, Cursor's agent shell) so the embedded shell gets truecolor regardless of how Electron was launched - rAF-debounced ResizeObserver — running fit.fit() synchronously during sibling pane transitions crashed the WebGL texture-atlas rebuild * fix(install.ps1): strip UTF-8 BOM regression that broke 'irm | iex' The canonical install flow irm https://raw.githubusercontent.com/.../scripts/install.ps1 | iex fails on PowerShell 5.1 with a cascade of 'The assignment expression is not valid' errors at every param() default value: [string]$Branch = 'main', ~~~~~~ The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept assignments... Root cause: scripts/install.ps1 carries a UTF-8 BOM (0xEF 0xBB 0xBF) as its first three bytes. 'irm' returns the response body as a string; on PS 5.1 the BOM survives into that string as a leading \ufeff character. 'iex' then evaluates the string and PS's parser chokes on the invisible character before param() -- error recovery proceeds into the body but every assignment is reported as broken. This was the exact failure mode the install.ps1 hardening pass (PR #27224) deliberately fixed by stripping the BOM and ensuring the file body is pure ASCII. Commit |
|||
| cf328723d4 |
docs: drop early-beta framing for native Windows support (#36093)
Native Windows is out of beta. Removes the early-beta warnings, headings, and rough-edge framing across the README and docs (EN + zh-Hans), keeping the WSL2-only dashboard PTY caveat. Historical RELEASE_v0.14.0.md notes are left intact since they accurately describe the state at that release. - README: Windows install + cross-platform notes - index.mdx, installation.md: headings, warning admonitions, parity note - windows-native.md: title/sidebar_label/warning, provider-hunting tip - contributing.md, nous-portal.md: cross-platform / Portal parity prose - Repoint cross-links to the renamed installation#windows-native-powershell anchor (EN) and #windows原生powershell (zh, also fixes pre-existing drift) |
|||
| 1fc7bdc5e6 |
feat(tools): always show Nous Tool Gateway backends, login on select (#35792)
* feat(tools): always show Nous Tool Gateway backends, login on select The Nous-managed Tool Gateway rows in `hermes tools` (Firecrawl, OpenAI TTS, Browser Use, FAL image/video) were hidden unless the user was already logged into Nous Portal with paid access. Now they are always listed. Selecting one runs an inline Nous Portal device-code OAuth + entitlement check — auth only, no inference-provider switch and no bulk 'enable all tools' prompt (that stays in `hermes model`). The row only activates the gateway once paid access is confirmed. - _visible_providers: stop hiding managed_nous_feature rows (incl. those also flagged requires_nous_auth); pure pre-auth UX rows still gate on login - nous_subscription.ensure_nous_portal_access(): auth + entitlement gate that preserves the user's active inference provider - _configure_provider / _reconfigure_provider: run the inline gate for managed backends; write config only when entitled - picker marker: 'via Nous Portal (login on select)' for logged-out users - _hidden_nous_gateway_message: now a no-op (rows are never hidden) * docs: hermes tools is a first-class Tool Gateway entry point The Tool Gateway docs framed `hermes setup --portal` / `hermes model` as the activation path and only mentioned `hermes tools` for mixing in your own keys. With the inline-login change, picking a Nous-managed backend in `hermes tools` is a complete path on its own — it logs you into Nous Portal on select if needed, without switching your inference provider or prompting to enable every other tool. - tool-gateway.md: Get started now lists three peer entry points; new paragraph explaining login-on-select and the no-prompt fast path when OAuth is already active - nous-portal.md + run-hermes-with-nous-portal.md: note that managed rows appear logged-out and trigger inline login on select |
|||
| 0cd7d54b00 |
feat(kanban): goal_mode cards run workers in a /goal loop (#35710)
* feat(kanban): goal_mode cards run workers in a /goal loop A goal_mode card wraps its dispatched worker in the Ralph-style goal loop behind /goal: after each turn an auxiliary judge checks the worker's response against the card title+body, and if not done the worker keeps going in the SAME session until the judge agrees, the worker terminates the task itself, or the turn budget runs out (which blocks the card for human review — never a silent exit). - kanban_db: goal_mode + goal_max_turns columns (additive migration), Task fields, create_task params, INSERT wiring, created-event payload. - kanban_tools: goal_mode/goal_max_turns on the kanban_create tool so orchestrators can opt cards in when fanning out. - kanban CLI: --goal / --goal-max-turns on 'kanban create'. - dashboard API: goal_mode/goal_max_turns on the create endpoint (auto-surfaced back via asdict). - _default_spawn: sets HERMES_KANBAN_GOAL_MODE / _GOAL_MAX_TURNS only when the card opts in. - goals.run_kanban_goal_loop: standalone, callback-injected loop engine (no SessionDB persistence; ephemeral worker). cli.py quiet path calls it after the worker's first turn when the env vars are set. - Docs: orchestrator skill + kanban feature page. Tests: DB roundtrip + legacy migration, spawn env gating, and the loop's continuation/completion/budget-block/finalize-nudge branches. E2E run against a real kanban DB confirms a budget-exhausted goal worker lands in a sticky blocked state. * feat(kanban/dashboard): goal-mode toggle in the create form Wires the goal_mode card setting into the dashboard UI (the plugin's hand-written IIFE bundle, no build step): - InlineCreate: 'goal mode' checkbox after the skills field; checking it reveals an optional 'max turns' number input. Both reset on submit and only post goal_mode/goal_max_turns when enabled. - TaskDrawer: a 'Goal mode: on (max N turns)' MetaRow so a card's goal-mode setting is visible after creation (auto-fed by asdict via the existing _task_dict). Live-tested through the running dashboard with a browser: created a goal-mode card with max-turns=8, confirmed it persisted to the kanban DB (goal_mode=1, goal_max_turns=8) and rendered back in the drawer as 'on (max 8 turns)'. No JS console errors. |
|||
| d276018378 |
docs(toolsets): clarify all/* wildcard does not enable kanban (#35729)
The all/* wildcard expands to every registered toolset, but a handful of tools have an additional check_fn gate on top of toolset membership and are intentionally NOT turned on by all/* alone: - Capability-gated tools (browser, computer_use, code_execution, Feishu, Home Assistant, cronjob) require their backend/credential prerequisite. - The kanban toolset is workflow-gated and deliberately opt-in. Kanban tools mutate shared board state, so they stay off by default even under all/* — you must list 'kanban' by name (or be a dispatcher-spawned worker with HERMES_KANBAN_TASK set). This was the expectations gap behind #35581 — the docs previously said all/* expands to 'every registered toolset' without noting the carve-out. Closes #35581. |
|||
| b47cb1bbf2 |
feat(kanban): file attachments on tasks (#35395)
Tasks can now carry file attachments (PDFs, images, source docs) that workers read directly — closes the gap where source material had to be pasted as a path into the task body. - kanban_db: task_attachments table (additive), Attachment dataclass, add/list/get/delete accessors, attachments_root/task_attachments_dir path helpers (per-board, HERMES_KANBAN_ATTACHMENTS_ROOT override) - build_worker_context: surfaces each attachment's absolute path so the worker (full file/terminal tool access) reads it via read_file/pdftotext - dashboard API: POST/GET/DELETE attachment routes (multipart upload, 25MB cap, traversal-safe filenames, root-containment check on download) - dashboard UI: Attachments section in the task drawer — upload button, list with download, per-row remove - docs + tests (13 cases: DB accessors, REST round-trip, traversal rejection, collision suffixing, worker-context surfacing) Closes #35338 |
|||
| cddb7283d9 |
fix(gateway): config.yaml path for WhatsApp/Weixin text-batch delays
Convert the salvaged text-debounce delays from HERMES_* env vars to config.yaml (gateway.platforms.<name>.extra.text_batch_delay_seconds / text_batch_split_delay_seconds), per the '.env is for secrets only' policy. Adds a finite/non-negative guard so bad YAML values fall back to the defaults instead of crashing asyncio.sleep(). - whatsapp.py / weixin.py: read delays via _coerce_float_extra(config.extra) - update Weixin content-dedup regression test for the deferred dispatch path - add text-debounce coverage (whatsapp + weixin): defaults, config override, bad-value fallback, env-var-ignored, burst-collapse, lone-message - docs: WhatsApp + Weixin config keys |
|||
| 61268ff7a9 |
feat(cli): add hermes prompt-size diagnostic (#35276)
Adds a 'hermes prompt-size' command that reports the fixed prompt budget for a fresh session: system prompt total, skills index, memory, user profile, prompt tiers, and tool-schema JSON bytes. Runs offline (dummy credentials force the direct-construction path, no network call). Lets users see which block dominates their per-call payload — the skills index is often the largest single block when many skills are installed (issue #34667). Zero model-tool footprint: it's a top-level CLI subcommand, not an agent tool. --platform <name> simulates a channel's platform hint; --json emits a machine-readable breakdown. Closes #34667 |
|||
| 827ce602db |
fix(honcho): harden self-hosted setup paths
Self-hosted Honcho setup had four sharp edges: - local/cloud URLs ending in /vN double-prefixed by the SDK (/v3/v3/... 404) - authenticated local servers had no setup prompt for a JWT/bearer token - profile-derived host keys could be dot-containing workspace IDs Honcho rejects - memory-provider config files with API keys written world-readable per umask This keeps existing behavior but makes those paths safer: - strip a trailing /vN version segment from any configured baseUrl before SDK init (the SDK's route builders always prepend their own version prefix); auth-skipping stays loopback-only - add an optional local JWT/bearer prompt in honcho setup, stored under hosts.<host>.apiKey - derive new profile host keys with underscores, still reading legacy hermes.<profile> blocks - write memory-provider config files atomically with 0600 via a shared utils.atomic_json_write(mode=) arg (honcho/hindsight/mem0/supermemory) - skip honcho.json parsing in gateway cache-busting unless Honcho is the active memory provider; memoize by honcho.json mtime when active - bust the gateway agent cache on memory.provider change - add a hermes memory setup <provider> one-liner so fresh installs can configure a named provider without the picker (the per-provider hermes <provider> subcommand only registers once that provider is active) Closes #20688, #29885, #26459, #30246, #33382, #32244. Co-authored-by: BROCCOLO1D |
|||
| 860cf28dab |
docs: clarify compression threshold is derived from the main model's context window (#35099)
The compression threshold is threshold × context_length where context_length is the MAIN agent model's window, not the auxiliary/summary model's. On a 262,144-token model at the default 0.50 the threshold is 131,072 — close to a common 128K figure by coincidence of the percentage, which has led to confusion that the auxiliary model's context limit is the trigger. Add a note preempting that misreading and pointing to the separate summary-model-context constraint. |
|||
| bcc8301000 |
Inspired by Claude Code: /compress here [N] — boundary-aware 'summarize up to here' (#35048)
Adds a user-chosen compression boundary to the existing /compress command. /compress here [N] summarizes everything except the most recent N exchanges (default 2), which are preserved verbatim — letting the user pick the compression boundary instead of relying on the automatic token-budget heuristic. Inspired by Claude Code's Rewind 'Summarize up to here' action (v2.1.139, Week 20, May 2026): https://code.claude.com/docs/en/whats-new/2026-w20 - hermes_cli/partial_compress.py: pure split/parse helpers + seam-alternation guard (shared by CLI and gateway). - cli.py / gateway/run.py: route 'here [N]' / '--keep N' to partial compression; compress only the head, re-append the verbatim tail through the seam guard. - Preserves message-flow role alternation (seam guard merges any illegal user->user / assistant->assistant adjacency). - Reuses the existing _compress_context session-rotation/lock machinery — no changes to the compression core. - Bare /compress (full) and /compress <focus> behavior unchanged. Tests: 12 helper unit tests + 5 CLI integration tests + E2E (interleaved tool-call transcript, degenerate/multimodal seams, real handler path). |
|||
| 27a2c4f36f |
fix(mcp): stop reporting false OAuth success when no token was obtained (#34807)
* docs(code-execution): document HERMES_* env narrowing + passthrough workaround
The execute_code sandbox-child env scrub (
|
|||
| b6ed3913d2 |
feat(skills): categorize tap skills from skills.sh.json grouping sidecar
A GitHub tap can ship a repo-root skills.sh.json (the published skills.sh
schema) declaring category groupings. The Skills Hub now reads it at index
time and uses each grouping title as the skill's category label, instead of
the tag-derived guess. Generic: any tap that ships the file gets real
categorization — NVIDIA's groupings (Inference AI, Decision Optimization,
GPU Development, etc.) flow through automatically.
- GitHubSource: _get_skillsh_groupings() fetches+caches the sidecar per repo;
_parse_skillsh_groupings() flattens it to {skill_name: title};
_list_skills_in_repo() stamps meta.extra['category']; _meta_to_dict now
serializes extra so the category survives the index cache round-trip.
- extract-skills.py: prefers extra['category'] over the tag heuristic and
exempts sidecar categories from the small-category to Other collapse.
- Docs + 12 tests.
|
|||
| 4de8009ce4 |
feat(skills): integrate NVIDIA/skills as a trusted skills hub tap
NVIDIA/skills is now a default trusted tap in the Hermes Skills Hub — discoverable, browsable, searchable, and auto-updating through the same pipeline that already serves OpenAI, Anthropic, and HuggingFace skills. Rebased onto current main. |
|||
| 90b3c54de9 |
fix: drain thread no longer crashes on fd-less stdout streams (#34789)
* docs(code-execution): document HERMES_* env narrowing + passthrough workaround
The execute_code sandbox-child env scrub (
|
|||
| 549a69a925 |
docs(curator): align 'agent-created' definition with actual provenance semantics
The curator docs stated that any skill not bundled/hub-installed was 'agent-created' and subject to curation — including foreground-created skills and hand-written ones. Since PR #19621 (May 2026), the curator requires an explicit marker in .usage.json, which only the background self-improvement review fork sets. Changes: - Rewrite 'What agent-created means' to document the 3-step eligibility check (not bundled + not hub + created_by=agent marker) - Explain that foreground skill_manage(create) does NOT mark skills as agent-created (user-directed by design) - Warn that hand-written skills are NOT curated - Add note in Per-run reports explaining the '(not resolved)' display when no candidates exist (LLM pass skipped, not a config error) - Link to skill_provenance.py for the write-origin ContextVar Ref: PR #19621, tools/skill_provenance.py, tools/skill_manager_tool.py |