fix(curator): match hub-installed skills by SKILL.md name, not just slug - #19680
Closed
luyao618 wants to merge 112 commits into
Closed
fix(curator): match hub-installed skills by SKILL.md name, not just slug#19680luyao618 wants to merge 112 commits into
luyao618 wants to merge 112 commits into
Conversation
`_read_hub_installed_names()` only returned hub slugs (lock.json keys), but `is_agent_created()` compared against the SKILL.md `name` field. When these differ (e.g. non-ASCII display names like 'Get笔记' vs slug 'getnote'), hub-installed skills were misclassified as agent-created and eligible for curator consolidation/archival. Now also collect: - directory names from each entry's `install_path` - the SKILL.md `name` field from each installed skill directory Closes NousResearch#19293
…arch#19301) * feat: add video_analyze tool for native video understanding Adds a video_analyze tool that sends video files to multimodal LLMs (e.g. Gemini) for analysis via the OpenRouter-compatible video_url content type. Mirrors vision_analyze in structure, error handling, and registration pattern. Key design: - Base64 encodes entire video (no frame extraction, no ffmpeg dep) - Uses 'video_url' content block type (OpenRouter standard) - Supports mp4, webm, mov, avi, mkv, mpeg formats - 50 MB hard cap, 20 MB warning threshold - 180s minimum timeout (videos take longer than images) - AUXILIARY_VIDEO_MODEL env override, falls back to AUXILIARY_VISION_MODEL - Same SSRF protection, retry logic, and cleanup as vision_analyze Default disabled: registered in 'video' toolset (not in _HERMES_CORE_TOOLS). Users opt in via: hermes tools enable video, or enabled_toolsets=['video']. * feat(video): add models.dev capability pre-check + CONFIGURABLE_TOOLSETS entry - Pre-checks model video capability via models.dev modalities.input before expensive base64 encoding. Fails early with helpful message suggesting video-capable alternatives (gemini, mimo-v2.5-pro). - Passes optimistically if model unknown or lookup fails. - Adds ModelInfo.supports_video_input() helper. - Adds 'video' to CONFIGURABLE_TOOLSETS and _DEFAULT_OFF_TOOLSETS so 'hermes tools enable video' works from CLI. - 8 new tests for the capability check (37 total). * refactor(video): remove models.dev capability pre-check Removes _check_video_model_capability and ModelInfo.supports_video_input. The vision_analyze tool doesn't pre-check image capability either — both tools rely on the same pattern: send request, handle API errors gracefully with categorized user-facing messages. The pre-check was inconsistent (only worked for some providers/models) so drop it for parity. * cleanup: compress comments, fix fragile timeout coupling - Replace _VISION_DOWNLOAD_TIMEOUT * 2 with hardcoded 60s (no silent breakage if vision timeout changes independently) - Strip verbose comments and redundant log lines throughout - No behavioral changes
Apply agent.redact.redact_sensitive_text with force=True to log content captured by _capture_log_snapshot before it reaches upload_to_pastebin. On-disk logs are untouched. Compatible with the off-by-default local redaction policy from NousResearch#16794: this is upload-time-only and applies regardless of security.redact_secrets because the public paste service is the leak surface. A visible banner is prepended to each uploaded log paste so reviewers know redaction was applied. --no-redact preserves deliberate unredacted sharing for maintainer-coordinated cases. The bug-report, setup-help, and feature-request issue templates direct users to run hermes debug share and paste the resulting public URLs. With redaction off by default per NousResearch#16794, those uploads have been carrying credentials onto paste.rs and dpaste.com. force=True is non-negotiable: without it, redact_sensitive_text short-circuits at agent/redact.py:322 when the env var is unset, so the fix would silently be a no-op for its target audience. A regression test pins this down. Fixes NousResearch#19316
…res terminal.cwd (NousResearch#19242) CLI/TUI sessions on the local backend now unconditionally use os.getcwd() as the working directory. The terminal.cwd config value is only consumed by gateway/cron/delegation modes (where there's no shell to cd from). Previously, 'hermes setup' would write an absolute path (e.g. $HOME) into terminal.cwd which then pinned the CLI to that directory regardless of where the user launched hermes from. This was a silent foot-gun — the user's 'cd' was being ignored. Changes: 1. cli.py: Restructured CWD resolution — if TERMINAL_CWD is not already set by the gateway, and the backend is local, always use os.getcwd(). Config terminal.cwd is irrelevant for interactive CLI/TUI sessions. 2. setup.py: Moved the cwd prompt from setup_terminal_backend() to setup_gateway(). It now only appears when configuring messaging platforms and is labeled 'Gateway working directory'. 3. Tests: Rewrote test_cwd_env_respect.py to validate the new behavior: explicit config paths are ignored for CLI, gateway pre-set values are preserved, non-local backends keep their config paths. 4. Docs: Updated configuration.md, profiles.md, and environment-variables.md to clarify that terminal.cwd only affects gateway/cron mode on local backend. Closes NousResearch#19214
…ry, ignores terminal.cwd (NousResearch#19242)" (NousResearch#19329) This reverts commit 9eaddfa.
…Hermes root The Kanban board is documented as shared across all Hermes profiles, but `kanban_db_path()` and `workspaces_root()` resolved through `get_hermes_home()`, which returns the active profile's HERMES_HOME. When the dispatcher spawned a worker with `hermes -p <profile> --skills kanban-worker chat -q "work kanban task <id>"`, the worker rewrote HERMES_HOME to the profile subdirectory before kanban_db.py imported, opening a profile-local `kanban.db` that did not contain the dispatcher's task. `kanban_show` and `kanban_complete` failed; the dispatcher's row stayed `running` and was retried/crashed. The same defect applied to `_default_spawn`'s log directory and `worker_log_path`, so `hermes kanban tail` did not see the worker's output. Add `kanban_home()` in `hermes_cli/kanban_db.py` that resolves through `HERMES_KANBAN_HOME` (explicit override) then `get_default_hermes_root()`, which already understands the `<root>/profiles/<name>` and Docker / custom HERMES_HOME shapes. Reroute `kanban_db_path`, `workspaces_root`, the `_default_spawn` log directory, `gc_worker_logs`, and `worker_log_path` through it. Profile-specific config, `.env`, memory, and sessions stay isolated as before; only the kanban surface is shared. Add a `TestSharedBoardPaths` regression class to `tests/hermes_cli/test_kanban_db.py` covering: default install, profile-worker convergence, Docker custom HERMES_HOME, Docker profile layout, explicit `HERMES_KANBAN_HOME` override, and a real SQLite round-trip across dispatcher and worker HERMES_HOME perspectives. The dispatcher/worker convergence tests fail on origin/main and pass after the fix. Update the `kanban.md` user-guide page and the misleading docstrings in `kanban_db.py` to describe the shared-root behavior. Fixes NousResearch#19348
Layers defense-in-depth on top of the shared-root anchoring (base commit). Changes in hermes_cli/kanban_db.py: - kanban_db_path() now honours HERMES_KANBAN_DB first, then falls through to kanban_home()/kanban.db. - workspaces_root() now honours HERMES_KANBAN_WORKSPACES_ROOT first, then falls through to kanban_home()/kanban/workspaces. - All three overrides (HERMES_KANBAN_HOME, HERMES_KANBAN_DB, HERMES_KANBAN_WORKSPACES_ROOT) now call .expanduser() for consistency. - _default_spawn() injects HERMES_KANBAN_DB and HERMES_KANBAN_WORKSPACES_ROOT into the worker subprocess env. Even when the worker's get_default_hermes_root() resolution somehow disagrees with the dispatcher's (symlinks, unusual Docker layouts), the two processes still open the same SQLite file. Module docstring updated to describe all three overrides and the dispatcher env-injection contract. Tests (tests/hermes_cli/test_kanban_db.py, TestSharedBoardPaths): - test_hermes_kanban_db_pin_beats_kanban_home - test_hermes_kanban_workspaces_root_pin_beats_kanban_home - test_empty_per_path_overrides_fall_through - test_dispatcher_spawn_injects_kanban_db_and_workspaces_root (monkeypatches subprocess.Popen, asserts both env vars reach the child even after HERMES_HOME is rewritten by `hermes -p <profile>`.) Docs: website/docs/reference/environment-variables.md gets entries for the three kanban env vars. This fusion is built on the cleanest of the seven competing PRs that targeted issue NousResearch#18442: * Base commit (from PR NousResearch#19350 by @GodsBoy): add `kanban_home()` helper anchored at `get_default_hermes_root()`, reroute all 5 kanban path sites through it (including the 3 sibling log-dir sites that the other six PRs missed), 8-test regression class. * Dispatcher env-var injection approach drawn from PRs NousResearch#18300 (@quocanh261997) and NousResearch#19100 (@cg2aigc). * Per-path env overrides drawn from PR NousResearch#19100 (@cg2aigc). * get_default_hermes_root() resolution direction first proposed in PR NousResearch#18503 (@beibi9966) and PR NousResearch#18985 (@Gosuj). Closes the duplicate/competing PRs: NousResearch#18300, NousResearch#18503, NousResearch#18670, NousResearch#18985, NousResearch#19037, NousResearch#19056, NousResearch#19100. Fixes NousResearch#18442 and NousResearch#19348. Co-authored-by: quocanh261997 <17986614+quocanh261997@users.noreply.github.com> Co-authored-by: cg2aigc <232694053+cg2aigc@users.noreply.github.com> Co-authored-by: beibi9966 <beibei1988@proton.me> Co-authored-by: Gosuj <123411271+Gosuj@users.noreply.github.com> Co-authored-by: LeonSGP43 <154585401+LeonSGP43@users.noreply.github.com>
The MiniMax OAuth API endpoints have moved from api.minimax.io to account.minimax.io and the old paths now respond with HTTP 307. httpx defaults to follow_redirects=False (unlike requests), so the device-code and token-refresh flows fail with "Temporary Redirect". Adds follow_redirects=True to the two httpx.Client instances in hermes_cli/auth.py used by the MiniMax OAuth flow. This is forward- compatible -- if endpoints move again, the redirect chain is followed automatically. Repro before patch: curl -i -X POST https://api.minimax.io/oauth/code # -> 307 curl -i -X POST https://api.minimax.io/oauth/token # -> 307 Verified end-to-end against a real MiniMax Plus account on macOS; the existing tests/test_minimax_oauth.py suite (15 tests) still passes.
…g_call_data construction
The _send_qqbot function was hardcoded to use the guild channel
endpoint (/channels/{id}/messages), which fails for C2C private
chats and QQ groups with 'channel does not exist' (code 11263).
This change tries the appropriate endpoints in order:
1. /channels/{id}/messages (guild channels)
2. /v2/users/{id}/messages (C2C private chats)
3. /v2/groups/{id}/messages (QQ groups)
Fixes active sending to QQBot C2C and group recipients.
…solution Curator review fork now forwards per-slot credentials from auxiliary.curator and legacy curator.auxiliary to resolve_runtime_provider, matching the canonical aux task schema. Add regression tests for binding and main fallback.
…or crash On Windows, services and terminals default to cp1252 encoding. The CLI uses box-drawing characters (┌│├└─) in banners, doctor output, and status displays. When print() tries to encode these under cp1252, an unhandled UnicodeEncodeError crashes the gateway on startup. This fix adds early UTF-8 enforcement in hermes_cli/__init__.py: - Sets PYTHONUTF8=1 and PYTHONIOENCODING=utf-8 - Re-opens stdout/stderr with UTF-8 encoding if not already UTF-8 Runs at import time so it protects all CLI subcommands. No effect on Unix (gated on sys.platform == "win32"). Backwards-compatible: on systems already using UTF-8, the function is a no-op. Fixes NousResearch#10956
…earch#19427) KANBAN_GUIDANCE layer 3 of the system prompt started with 'You are a Kanban worker', overriding the profile's SOUL.md identity at layer 1. Profiles with strict role boundaries (e.g. a reviewer profile that never writes code) still executed implementation tasks because the kanban identity claim diluted SOUL's. Drop the identity line. Layer 3 now describes the task-execution protocol only; SOUL.md remains the sole identity slot. Fixes NousResearch#19351
Deduplicate exact and near-exact Discord voice STT transcripts per guild/user over a short window to avoid duplicate delayed agent replies. Adds regression tests for exact and near-duplicate voice transcript suppression.
Users commonly place `require_mention: true` at the top level of config.yaml alongside `group_sessions_per_user`, expecting it to gate Telegram group messages. The key was silently ignored because the config loader only checked `yaml_cfg["telegram"]["require_mention"]`. When `require_mention` is found at the top level and no telegram-specific value is set, the fix now: - adds it to platforms_data["telegram"]["extra"] so _telegram_require_mention() picks it up via the primary config.extra path - sets TELEGRAM_REQUIRE_MENTION env var for the secondary fallback path A telegram-specific value (telegram.require_mention) still takes precedence over the top-level shorthand. Also corrects telegram.md: bare /cmd without @botName is rejected when require_mention is enabled; only /cmd@botname (bot-menu form) passes. Fixes NousResearch#3979
_try_anthropic() lacked the explicit_api_key parameter added to _try_openrouter() in NousResearch#18768. When resolve_provider_client() is called with provider="anthropic" and an explicit key (e.g. from a fallback_model entry with api_key set), the key was silently ignored — _try_anthropic() always fell back to resolve_anthropic_token(), so the fallback returned None,None for users without a default Anthropic credential configured. Fix: add explicit_api_key: str = None to _try_anthropic() and use explicit_api_key or <pool/env fallback> in both the pool-present and no-pool paths. Pass explicit_api_key=explicit_api_key at the call site in resolve_provider_client(). Symmetric with the _try_openrouter() fix. No behavior change when explicit_api_key is None.
Cron jobs that reference skills via their skills: config never bumped the usage counters in .usage.json, so the curator could auto-archive skills actively used by cron jobs based on stale timestamps. Now _build_job_prompt() calls bump_use(skill_name) for each successfully loaded skill so the curator sees them as active.
`_tui_need_npm_install()` compares the canonical `package-lock.json` against
the hidden `node_modules/.package-lock.json` to decide whether `npm install`
needs to re-run. npm 9 drops the `"peer": true` field from the hidden lock
on dev-deps that are *also* declared as peers (the canonical lock preserves
the dual annotation). That made the check flag 16 packages (`@babel/core`,
`@types/node`, `@types/react`, `@typescript-eslint/*`, `react`, `vite`,
`tsx`, `typescript`, …) as mismatched on every launch, triggering a runtime
`npm install`.
Inside the Docker image, that runtime install then fails with EACCES because
`/opt/hermes/ui-tui/node_modules/` is root-owned from build time, so
`docker run … hermes-agent --tui` prints:
Installing TUI dependencies…
npm install failed.
…and exits 1, with no preview. The empty preview is a second bug: the
launcher captured only stderr, but npm 9 writes EACCES to stdout, which
was DEVNULL'd.
Fixes:
- Add `"peer"` to `_NPM_LOCK_RUNTIME_KEYS` so the comparison ignores the
non-deterministic field, alongside the existing `"ideallyInert"`.
- Capture stdout as well as stderr in the install subprocess so future
failures surface a useful preview instead of a bare "failed." line.
Regression tests:
- `test_no_install_when_only_peer_annotation_differs` — the exact scenario
- `test_install_when_version_differs_even_with_peer_drop` — guards against
the peer-drop tolerance masking a real version skew
On-host impact: the same false-positive was firing on every `hermes --tui`
invocation from a normal checkout, silently running a no-op `npm install`
each time (it converged because the host's `node_modules/` is writable).
Startup time on the TUI should drop noticeably.
Adds an optional dashboard side-process to the container entrypoint,
toggled by `HERMES_DASHBOARD=1` (also accepts `true` / `yes`). When set,
the entrypoint backgrounds `hermes dashboard` before `exec`-ing the main
command so the user's chosen foreground process (gateway, chat, `sleep
infinity`, …) remains PID-of-interest for the container runtime.
docker run -d \
-v ~/.hermes:/opt/data \
-p 8642:8642 -p 9119:9119 \
-e HERMES_DASHBOARD=1 \
nousresearch/hermes-agent gateway run
Defaults chosen for the container case:
- Host: 0.0.0.0 (reachable through published port; can override to
127.0.0.1 via HERMES_DASHBOARD_HOST for sidecar/reverse-proxy setups)
- Port: 9119 (matches `hermes dashboard`)
- Auto-adds `--insecure` when binding to non-localhost, matching the
dashboard's own safety gate for exposing API keys
- HERMES_DASHBOARD_TUI is read by `hermes dashboard` directly — no
entrypoint plumbing needed
Dashboard output is prefixed with `[dashboard]` via `stdbuf`+`sed -u` so
it's easy to separate from gateway logs in `docker logs`. No supervision:
if the dashboard crashes it stays down until the container restarts
(documented in the `:::note` panel).
Other changes bundled in:
- Deprecate GATEWAY_HEALTH_URL / GATEWAY_HEALTH_TIMEOUT env vars in
hermes_cli/web_server.py with a DEPRECATED block comment and a
`.. deprecated::` note on _probe_gateway_health. The feature still
works for this release; it'll be removed alongside the move to a
first-class dashboard config key.
- Rewrite the "Running the dashboard" doc section around the new
single-container pattern. Drops the previously-documented
dashboard-as-its-own-container setup — that pattern relied on the
deprecated env vars for cross-container gateway-liveness detection,
and without them the dashboard would permanently report the gateway
as "not running".
- Collapse the two-service Compose example (gateway + dashboard
container) into a single service with HERMES_DASHBOARD=1. Removes
the now-unnecessary bridge network and `depends_on`.
- Drop the ":::warning" caveat about "Running a dashboard container
alongside the gateway is safe" — that case no longer exists.
…sync of TERMINAL_CWD (NousResearch#19334) The old CWD heuristic was fooled by: 1. TERMINAL_CWD persisted to .env by `hermes config set terminal.cwd` 2. Inherited TERMINAL_CWD from parent hermes processes 3. Only resolved when config had a placeholder value (not explicit paths) Fix: - load_cli_config() unconditionally uses os.getcwd() for local backend - TERMINAL_CWD always force-exported in CLI mode (overrides stale values) - Gateway sets _HERMES_GATEWAY=1 marker so lazy cli.py imports don't clobber - Remove terminal.cwd from config-set .env sync map (prevents re-poisoning) - Clarify setup wizard label as 'Gateway working directory' Closes NousResearch#19214
* docs: document /kanban slash command
The kanban user guide and slash-commands reference only mentioned the
/kanban slash command in passing. Add a proper section covering:
- CLI and gateway both expose the full hermes kanban surface via
hermes_cli.kanban.run_slash (identical argument surface)
- Mid-run usage: /kanban bypasses the running-agent guard, so reads
and writes land immediately while an agent is still in a turn
- Auto-subscribe on /kanban create from the gateway — originating
chat is subscribed to terminal events, with a worked example
- Output truncation (~3800 chars) in messaging
- Autocomplete hint list vs full subcommand surface
Also adds /kanban rows to both slash-command tables (CLI + messaging)
in reference/slash-commands.md and moves it into the 'works in both'
notes bucket.
* docs(kanban): frame the model's tool surface as primary, CLI as the human surface
The kanban user guide and CLI reference read as if you drive the board
by running `hermes kanban` commands everywhere. In practice:
- **You** (human, scripts, cron, dashboard) use the `hermes kanban …`
CLI, the `/kanban …` slash command, or the REST/dashboard.
- **Workers** spawned by the dispatcher use a dedicated `kanban_*`
toolset (`kanban_show`, `kanban_complete`, `kanban_block`,
`kanban_heartbeat`, `kanban_comment`, `kanban_create`,
`kanban_link`) and never shell out to the CLI.
Changes to `user-guide/features/kanban.md`:
- New 'Two surfaces' intro distinguishes the two front doors up front.
- Quick-start section re-labelled so each step says who is running it
(you vs. orchestrator vs. worker).
- 'How workers interact with the board' rewritten:
- Lead with "Workers do not shell out to `hermes kanban`."
- Tool table extended with required params.
- Concrete worker-turn example (`kanban_show` → `kanban_heartbeat`
→ `kanban_complete`) and an orchestrator fan-out example
(`kanban_create` x N with `parents=[...]`).
- Moved 'Why tools not CLI' from a defensive aside to a clean
follow-up section.
- 'Worker skill' section explicitly says the lifecycle is taught
in tool calls, not CLI commands.
- 'Pinning extra skills' reordered — orchestrator tool form first
(the usual case), human/CLI second, dashboard third.
- 'Orchestrator skill' now shows a canonical `kanban_create` /
`kanban_link` / `kanban_complete` tool-call sequence instead of
only describing what the skill teaches.
- CLI-command-reference heading now clarifies this is the human
surface, with a cross-link to the tool-surface section.
- 'Runs — one row per attempt' structured-handoff example replaced:
the primary example is now `kanban_complete(summary=..., metadata=...)`
(what a worker actually does), with the CLI form retained as
"when you, the human, need to close a task a worker can't."
Changes to `reference/cli-commands.md`:
- `hermes kanban` intro marks itself as the human / scripting surface
and links out to the worker tool surface.
- Corrected `comment <id>` description — the next worker reads it via
`kanban_show()`, not by running `hermes kanban show`.
* docs(kanban-tutorial): reframe worker actions as tool calls
Honest answer to Teknium's follow-up: no, the first pass missed the
tutorial. The four stories all showed `hermes kanban claim /
complete / block / unblock` as if the backend-dev, pm, and reviewer
personas were humans running CLI commands. In a real hermes kanban
run those agents are dispatcher-spawned workers driving the board
through the `kanban_*` tool surface.
Changes:
- Setup intro now distinguishes the three surfaces up front
(dashboard / CLI for you, `kanban_*` tools for workers) and
establishes the convention: `bash` blocks are commands *you* run,
`# worker tool calls` blocks are what the agent emits.
- Story 1 (solo dev schema): 'Claim the schema task, do the work,
hand off' block replaced with the dispatcher spawning the
backend-dev worker and a `kanban_show → kanban_heartbeat →
kanban_complete` tool-call sequence. The 'On the CLI' `hermes
kanban show / runs` block re-labelled as 'you peeking at the board'
to keep it correct as a human inspection step.
- Story 2 (fleet farming): note about structured handoff updated
from `--summary` / `--metadata` CLI flags to
`kanban_complete(summary=..., metadata=...)` tool form.
- Story 3 (role pipeline): the big PM/engineer/reviewer block fully
rewritten as three worker tool-call sequences — PM worker
completes spec, engineer worker blocks, human/reviewer
`hermes kanban unblock` (or `/kanban unblock`), engineer worker
respawns and completes. The respawn-as-new-run mechanic is now
explicit.
- Reviewer paragraph: `build_worker_context` replaced with
`kanban_show()` — that's the tool that delivers the parent
handoff to the model.
- Structured handoff section heading and body updated:
`--summary`/`--metadata` → `summary`/`metadata` (tool params),
with a note that the tool surface doesn't expose a bulk variant
for the same reason the CLI refuses multi-task `complete`.
Story 4 (circuit breaker) unchanged — its workers fail to spawn,
so there are no tool calls to show; the `hermes kanban create` and
`hermes kanban runs` commands in it are correctly human-driven.
hermes status was missing NVIDIA API key from its API keys display. Now shows NVIDIA NIM ✓/✗ with key hash like other providers. Fixes NousResearch#16082
The `provider` field in CRONJOB_SCHEMA only showed examples like
'openrouter' and 'anthropic', with no mention of the canonical
'custom:<name>' form required for custom_providers entries. When the
user has custom providers configured, LLMs tend to write the bare type
name ('custom') because the schema does not advertise the ':<name>'
suffix. The bare value then serializes into jobs.json and causes the
cron job to fail silently at run time — `_resolve_model_override`
treats it as a user-specified provider and skips the pin-to-current
fallback, but no provider ever resolves from the bare 'custom' string.
Clarifying the schema so the canonical form is discoverable addresses
the root cause at the tool-definition boundary.
Add 'xiaomi' to the _anthropic_preserve_dots() provider whitelist and 'xiaomimimo.com' to the URL-based fallback check. Without this, normalize_model_name() converts mimo-v2.5 to mimo-v2-5, which the Xiaomi API rejects with HTTP 400. Fixes NousResearch#16156
esbuild raises 'Must use outdir when there are multiple input files' on Android/Termux ARM64 with esbuild >=0.25. The build script used --outfile=dist/ink-bundle.js which is only valid for a single entry point with no code splitting. Switching to --outdir=dist fixes the error and names the output file dist/entry-exports.js (matching the input file name). Update index.js to import from the new path. Fixes NousResearch#16072
…g.yaml
YAML parses `delegation: null` as Python None. `dict.get(key, {})`
only uses the default when the key is *missing*, not when it exists with
a None value, so `cfg.get("max_concurrent_children")` crashes with
`'NoneType' object has no attribute 'get'`.
Same pattern as fd9b692 (fix(tui): tolerate null top-level sections).
Use `dict.get(key) or {}` to handle both missing and None-valued keys.
Closes: delegation null config crash (same class as NousResearch#7215, NousResearch#7346)
MiniMax China (api.minimaxi.com) does not expose a /v1/models endpoint. The doctor command was probing it and reporting HTTP 404 as a warning, even though the API works correctly for chat completions. Set supports_health_check=False for MiniMax CN so doctor shows "(key configured)" instead of the false 404 warning. Refs NousResearch#12768, NousResearch#13757
…ot-found Previously only HTTP 404/503 and specific error strings triggered a fallback to the main model when the summary model was unavailable. Timeout errors (HTTP 408/429/502/504, or error strings containing 'timeout') entered a short cooldown instead, leaving context to grow unbounded for the rest of the session. Add _is_timeout detection alongside _is_model_not_found so that transient timeout errors on the summary model also trigger immediate fallback to the main model, preventing compression failure from cascading. Closes NousResearch#15935
`_resolve_model_override` treated any non-empty `provider` string from the LLM as user-specified and skipped the pin-to-current-provider fallback. When the LLM wrote bare `'custom'` (instead of the canonical `'custom:<name>'` referring to a custom_providers entry), the value serialized into jobs.json as `"provider": "custom"` and the scheduler could never resolve a provider from it — the cron job failed silently at run time. Treat bare `'custom'` as "no provider supplied" so the current main provider gets pinned instead, matching behaviour for the omitted case. Defence-in-depth complement to a schema-description fix (NousResearch#15477) that discourages the LLM from emitting bare `'custom'` in the first place.
The 'q' alias is defined for 'queue' command in commands.py:93.
The hardcoded 'q' in cli.py:5910 was dead code - resolve_command('q')
returns the queue CommandDef, so canonical would never be 'q'.
Removes the misleading check without changing any behavior:
- /quit and /exit still exit (defined aliases)
- /q still maps to queue (as intended)
`_apply_profile_override()` scans `sys.argv` for `-p / --profile` at
module import time. When `hermes_cli.main` is imported inside pytest
with `-p no:xdist` on the command line, it picks up `'no:xdist'` as a
profile name candidate, then passes it to `resolve_profile_env()` which
raises `ValueError` (invalid format), and the function calls
`sys.exit(1)` — aborting test collection with an INTERNALERROR before
any test runs.
The same conflict affects any tool or wrapper that uses `-p` for its
own flag and then imports `hermes_cli.main`.
Fix: add a format guard immediately after step 1 (explicit flag scan).
If `consume == 2` (the value came from `-p <value>`, not
`--profile=value`) and the candidate doesn't match the canonical
profile-name pattern `[a-z0-9][a-z0-9_-]{0,63}` (mirrored from
`hermes_cli.profiles._PROFILE_ID_RE`), discard it and continue as if
no `-p` flag was found. The `active_profile` file-based fallback
(step 2) only reads a file written by hermes itself, so it always
produces valid names and needs no guard.
Regression guard: with the guard reverted, importing
`hermes_cli.main` with `sys.argv = ['pytest', '-p', 'no:xdist', ...]`
raises `SystemExit(1)`. With the guard in place, the import succeeds
and `sys.argv` is left intact for pytest. Legitimate `-p coder` still
flows through to `resolve_profile_env()` unchanged.
Rebased onto current `origin/main` (`e5dad4ac5`) — the prior branch
base (`4fade39c9`) was 824 commits behind and the PR was DIRTY /
CONFLICTING. The 1.5 HERMES_HOME-set early-return block has since
landed between the original insertion point and step 2; the new guard
is positioned correctly before the early return so a bogus `-p` value
no longer prevents the early return from kicking in.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auth check in list_authenticated_providers used mere key presence in credential_pool to conclude a provider is authenticated. An empty entry (pool_store key with no actual credentials) caused providers like ollama-cloud to appear as authenticated in the model picker even when no OLLAMA_API_KEY was set. The user's picker then offered nemotron-3-super under Ollama Cloud; selecting it routed every subsequent turn to https://ollama.com/v1, which rejected the requests with HTTP 400. Fix: drop the pool_store key-existence check from both section 2 (HERMES_OVERLAYS) and section 2b (CANONICAL_PROVIDERS). The following load_pool().has_credentials() call already handles the legitimate pooled- credential case; checking for an empty key just ahead of it was redundant and actively harmful.
Treat explicit CDP override mode as a valid browser backend even when agent-browser is absent, and add a regression test to prevent false-negative availability gating.
When agent-browser is globally installed via 'npm install -g agent-browser'
but not present in the local node_modules, doctor falsely warns that it's
not installed. Add shutil.which('agent-browser') as a fallback check after
the local path check.
Closes NousResearch#15951
Allow users to start a fresh session and immediately set its title by
passing a name to /new (or /reset):
/new Refactor auth module
Changes:
- hermes_cli/commands.py: add args_hint='[name]' to /new command
- cli.py: parse title argument in process_command(), pass to new_session()
- cli.py: new_session() accepts title=None, sets title via SessionDB
- gateway/run.py: _handle_reset_command() parses title, sets on new entry
- gateway/session.py: reset_session() accepts optional display_name
- tests: add test_new_session_with_title, test_reset_command_with_title,
test_new_command_in_help_output
All 36 affected tests pass.
The contributor's PR silently swallowed ValueError from SessionDB.set_session_title() with bare except Exception: pass. Users typing /new <title> with an already-in-use title got an untitled session and no feedback. Changes: - cli.py: catch ValueError from both sanitize_title() and set_session_title(); print the error and mark the session untitled in the banner (never echo the rejected title back). - gateway/run.py: append a warning note to the reset reply on title rejection; reflect the accepted title in the header. - Add regression tests for the duplicate-title path in CLI and gateway. Also map exx@example.com -> @exxmen in scripts/release.py.
…overflow
Set max_result_size_chars=100_000 on the read_file registry entry (was
float('inf')), closing the Layer 2 defense-in-depth gap in
tool_result_storage.py. The existing Layer 1 guard inside
_handle_read_file already returns a JSON error for oversized reads;
this aligns the registry cap with every other tool.
Update test_read_file_never_persisted → test_read_file_result_size_cap
to assert 100_000, and add test_read_file_registry_cap_is_100k as an
explicit regression guard against re-introducing float('inf').
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends the existing _normalize_tool_input_schema to also drop top-level union keywords that Anthropic's tool schema validator rejects with HTTP 400. Several upstream and plugin tools ship schemas with a top-level oneOf/ allOf/anyOf (common for Pydantic discriminated unions). The existing strip_nullable_unions pass only handles anyOf-with-null patterns; a non-null top-level union keyword sails through and hits the API. Salvage of NousResearch#16471 — approach folded into the existing normalize helper rather than introducing a parallel _sanitize_input_schema function, to avoid two schema-munging code paths running against the same input. Co-authored-by: Grey0202 <grey0202@users.noreply.github.com>
Collaborator
Collaborator
Contributor
Author
|
Closing — diff is polluted from rebase-merge with main, and competing fix #19460 is open for the same issue. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
is_agent_created(skill_name)intools/skill_usage.pycompares the skill's SKILL.mdnamefield against hub lock keys, which are ASCII slugs. When the SKILL.mdnameis non-ASCII (e.g."Get笔记"vs slug"getnote"), the comparison misses and the function wrongly reports the hub skill as agent-created.This allows
hermes curator archiveand_mutate()to treat hub-installed skills as user-created, potentially archiving them.Changes
tools/skill_usage.py: Widen_read_hub_installed_names()to also collect directory names from each entry'sinstall_pathand the SKILL.mdnamefield from each installed skill directory, sois_agent_created()correctly identifies hub skills regardless of ASCII/non-ASCII name mismatches.Testing
skill_usage/curatorsuite)Closes #19641
Related: #19293, #19297