chore: sync with upstream main (2026-05-04)#19
Merged
Conversation
When send_message tool is called from inside a running gateway, the _run_async bridge spawns a worker thread with a separate event loop. send_weixin_direct then reuses the live adapter's aiohttp session which was created on the gateway's main loop. aiohttp's TimerContext checks asyncio.current_task(loop=session._loop) and sees None because we're executing on the worker thread's loop → raises 'Timeout context manager should be used inside a task'. Fix: skip the live-adapter shortcut when the session belongs to a different event loop, falling through to the fresh-session path.
Enable OpenRouter's response caching feature (beta) via X-OpenRouter-Cache
headers. When enabled, identical API requests return cached responses for
free (zero billing), reducing both latency and cost.
Configuration via config.yaml:
openrouter:
response_cache: true # default: on
response_cache_ttl: 300 # 1-86400 seconds
Changes:
- Add openrouter config section to DEFAULT_CONFIG (response_cache + TTL)
- Add build_or_headers() in auxiliary_client.py that builds attribution
headers plus optional cache headers based on config
- Replace inline _OR_HEADERS dicts with build_or_headers() at all 5 sites:
run_agent.py __init__, _apply_client_headers_for_base_url(), and
auxiliary_client.py _try_openrouter() + _to_async_client()
- Add _check_openrouter_cache_status() method to AIAgent that reads
X-OpenRouter-Cache-Status from streaming response headers and logs
HIT/MISS status
- Document in cli-config.yaml.example
- Add 28 tests (22 unit + 6 integration)
Ref: https://openrouter.ai/docs/guides/features/response-caching
- TestClampCommandNamesTriples: unit tests for 3-tuple support in _clamp_command_names (short names, long names, collisions, multiple entries, backward compat with 2-tuples) - TestDiscordSkillCmdKeyDispatch: integration test through the full discord_skill_commands pipeline verifying long skill names retain their original cmd_key after clamping - Add contributor CharlieKerfoot to AUTHOR_MAP
Slash commands (_run_simple_slash, _handle_thread_create_slash) bypassed every DISCORD_ALLOWED_* gate enforced by on_message. Any guild member could invoke /background (RCE via terminal), /restart, /model, /skill, etc. CVSS 9.8 Critical. - _evaluate_slash_authorization mirrors on_message gates (user, role, channel, ignored channel) with fail-closed semantics - _check_slash_authorization sends ephemeral reject + logs + admin alert - Auth gate runs before defer() so rejections are ephemeral - /skill autocomplete returns [] for unauthorized users (no catalog leak) - Component views (ExecApproval, SlashConfirm, UpdatePrompt, ModelPicker) now honor role allowlists via shared _component_check_auth helper - Optional DISCORD_HIDE_SLASH_COMMANDS defense-in-depth - Cross-platform admin alert (Telegram/Slack fallback) on unauthorized attempts Based on PR NousResearch#18125 by @0xyg3n.
…ket Mode connections SlackAdapter.connect() overwrote self._handler, self._app, and self._socket_mode_task without closing the prior AsyncSocketModeHandler first. If connect() was called a second time on the same adapter (e.g. during a gateway restart or in-process reconnect attempt), the old Socket Mode websocket stayed alive. Both the old and new connections received every Slack event and dispatched it twice — producing double responses with different wording, the same bug that affected DiscordAdapter (NousResearch#18187, fixed in NousResearch#18758). Fix: add a close-before-reassign guard at the start of the connection setup path, mirroring the guard DiscordAdapter.connect() already has. When self._handler is None (fresh adapter, first connect()) the block is a harmless no-op. Scoped to the handler/app fields only — no behavior change for any path that does not call connect() twice. Fixes NousResearch#18980
…suspend suspend_recently_active() was unconditionally setting suspended=True on startup, causing get_or_create_session() to wipe conversation history on every restart. Change to set resume_pending=True instead, so sessions auto-resume while still allowing stuck-loop escalation after 3 failures.
…avoid race (NousResearch#18912) When /new is issued while an agent is actively processing, the confirmation response was never sent to the user because cancel_session_processing() was called before _send_with_retry(). Task cancellation side effects could silently drop the response. Fix: reorder to send the response BEFORE cancelling the old task. Add logging at the send point (matching the pattern at line 2800 in _process_message_background) so future failures are visible. Closes: NousResearch#18912
Tests updated to reflect suspend_recently_active now setting resume_pending=True (preserves session) instead of suspended=True (wipes session history). AUTHOR_MAP entries: millerc79 (NousResearch#19033), shellybotmoyer (NousResearch#18915)
…itical vulns (NousResearch#19204) The whatsapp-bridge pulls @whiskeysockets/baileys at a pinned git commit whose transitive dep tree ships protobufjs <7.5.5, triggering GHSA-xq3m-2v4x-88gg (critical, arbitrary code execution). npm audit reported 3 cascading criticals: protobufjs, @whiskeysockets/libsignal-node (pulls protobufjs), and baileys itself (effect rollup). Fix: add npm overrides block pinning protobufjs to ^7.5.5. Deduplicates to a single 7.5.6 copy at node_modules/protobufjs that both libsignal-node and any other consumers resolve through normal module resolution. Why not bump baileys: npm-published baileys@6.17.16 is deprecated by the maintainers (wrong version), 7.0.0-rc.* still pulls the same vulnerable libsignal-node, and upstream Baileys HEAD adds a 4th vuln (music-metadata). The override is the minimal, behavior-preserving fix. Validation: - npm audit: 3 critical -> 0 vulnerabilities - node -e "import('@whiskeysockets/baileys')" -> all 5 named exports (makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion, downloadMediaMessage) resolve - node bridge.js loads all modules and reaches Express bind (exits only on EADDRINUSE because the live gateway owns :3000) - Single deduped protobufjs@7.5.6 in the tree
…ousResearch#19209) /goal was silently broken outside the classic CLI. TUI: /goal was routed through the HermesCLI slash-worker subprocess, which set the goal row in SessionDB but then called _pending_input.put(state.goal) — the subprocess has no reader for that queue, so the kickoff message was discarded. No post-turn judge was wired into prompt.submit either, so even a manual kickoff would not continue the goal loop. Intercept /goal in command.dispatch instead, drive GoalManager directly, and return {type: send, notice, message} so the TUI client renders the Goal-set notice and fires the kickoff. Run the judge in _run_prompt_submit after message.complete, surface the verdict via status.update {kind: goal}, and chain the continuation turn after the running guard is released. Gateway: _post_turn_goal_continuation was gated on hasattr(adapter, 'send_message'), but adapters only expose send(). That branch was dead on every platform — users never saw '✓ Goal achieved', 'Continuing toward goal', or budget-exhausted messages. Replace the dead call with adapter.send(chat_id, content, metadata) and drop a broken reference to self._loop. Tests: - tests/tui_gateway/test_goal_command.py — full /goal dispatch matrix (set / status / pause / resume / clear / stop / done / whitespace) plus regressions for slash.exec → 4018 and 'goal' staying in _PENDING_INPUT_COMMANDS. - tests/gateway/test_goal_verdict_send.py — locks in the adapter.send path for done / continue / budget-exhausted and verifies the hook no-ops when no goal is set or the adapter lacks send().
…edential files
Terminal commands can write to shell RC files (~/.bashrc, ~/.zshrc,
~/.profile) and credential files (~/.netrc, ~/.pgpass, ~/.npmrc,
~/.pypirc) via redirection or tee without triggering approval, even
though write_file already blocks these paths in file_safety.py.
This creates an inconsistency: write_file protects these paths but
terminal shell redirections bypass the same protection. An agent
prompted via indirect injection could install persistent backdoors
(e.g. PATH manipulation, alias overrides) or write credential entries
without user approval.
Extend _SENSITIVE_WRITE_TARGET with two new regex groups matching the
same paths that file_safety.py's WRITE_DENIED_PATHS already covers:
_SHELL_RC_FILES — ~/.bashrc, ~/.zshrc, ~/.profile, ~/.bash_profile,
~/.zprofile
_CREDENTIAL_FILES — ~/.netrc, ~/.pgpass, ~/.npmrc, ~/.pypirc
All 130 existing tests pass.
``_resolve_origin`` called ``origin.get('platform')`` on whatever
``job.get('origin')`` returned. The leading ``if not origin: return None``
short-circuited the falsy cases (None, empty dict, "") but a non-empty
string passed that guard and then crashed with
``AttributeError: 'str' object has no attribute 'get'`` on every fire
attempt. Observed in the wild after a migration script tagged jobs with
free-form provenance strings (e.g.
``"combined-digest-replaces-x-and-y-20260503"``).
``mark_job_run`` did record ``last_status: error,
last_error: "'str' object has no attribute 'get'"`` once, but the next
tick re-loaded the same poisoned origin and crashed identically. The
job stayed enabled, fired every tick, and accumulated cascading errors
in the log until ``origin`` was patched manually.
Replace the falsy guard with ``isinstance(origin, dict)``. Non-dict
origins (string, int, list, tuple, float — anything that survived a
hand-edit, JSON-script write, or migration) are now treated the same
as a missing origin: the job continues with ``deliver`` falling back
through its normal home-channel path instead of crashing the scheduler
loop.
Test parametrises the non-dict shapes that can appear in jobs.json
through external writers and asserts ``_resolve_origin`` returns None
for each.
Note: this fix scope is the non-dict-``origin`` crash only. The
``next_run_at: null`` recurring-job recovery (the second sub-bug in
NousResearch#18722) is independently addressed by the in-flight NousResearch#18825, which
extends the never-silently-disable defense from NousResearch#16265 to
``get_due_jobs()`` — that approach is well-aligned with the existing
recovery pattern and ships fine without a competing change here.
Fixes NousResearch#18722 (non-dict origin crash; recurring-job recovery covered by NousResearch#18825)
…ust dim errors Broadens the existing fallback (previously only fired for Photo_invalid_dimensions) to cover every send_photo exception class: rate limits, corrupt file markers, format edge cases. The expected dimension case still logs at INFO (document is the right path); all other cases log at WARNING with exc_info so they're visible in logs. If send_document itself fails, we still fall back to the base adapter's text-only 'Image: /path' rendering as a last resort. Salvage of NousResearch#15837 — original PR author QifengKuang proposed the broader try/except-style fallback. Adapted to keep the existing INFO-vs-WARNING log split for dimension errors (the expected case). Co-authored-by: QifengKuang <k2767567815@gmail.com>
…NousResearch#19712) Mirrors the Codex auto-import UX. On successful Nous login (either `hermes auth add nous --type oauth` or `hermes login nous`), tokens are mirrored to `$HERMES_SHARED_AUTH_DIR/nous_auth.json` (default `~/.hermes/shared/nous_auth.json`, outside any named profile's HERMES_HOME). On next login in a new profile, the flow offers to import those credentials ("Import these credentials? [Y/n]") and rehydrates via a forced refresh+mint instead of running the full device-code flow. Runtime refresh in any profile syncs the rotated refresh_token back to the shared store so sibling profiles don't hit stale-token fallback after rotation. The volatile 24h agent_key is NOT persisted to the shared store — only the long-lived OAuth tokens are cross-profile useful. - `HERMES_SHARED_AUTH_DIR` env var for tests + custom layouts - Pytest seat belt mirrors the existing `_auth_file_path` guard so forgetting to redirect the store in a test fails loudly - File mode 0600 where platform supports it - Runtime credential resolution is unchanged — shared store is only consulted during the login flow, so profile isolation at runtime is preserved - Stale refresh_token + portal-down cases gracefully fall back to device-code Addresses a user report from Mike Nguyen: running `hermes --profile <name> auth add nous --type oauth` for every new profile is unnecessary friction now that Codex has a shared-import flow via `~/.codex/auth.json`.
…erns ENV-assignment and JSON-field regex patterns in redact_sensitive_text() cause false positives when reading source code files: - MAX_TOKENS=*** triggers the ENV assignment pattern - "apiKey": "test" in test fixtures triggers the JSON field pattern Add code_file=False parameter. When code_file=True, skip only the ENV-assignment and JSON-field regex passes; all other patterns (prefixes, auth headers, private keys, DB connstrings, JWTs, URL secrets) are still applied. Update file_tools.py (read_file and search_files) to pass code_file=True so agent code analysis is not polluted by false-positive redactions. Closes NousResearch#15934
…args
Open-weight models (DeepSeek, Qwen, GLM) sometimes emit tool calls like
`{"urls": "https://a.com"}` when the tool schema declares
`type: array`. The call was JSON-valid but semantically wrong, and
`coerce_tool_args` would pass the bare string through — the tool then
failed with a confusing type error.
`coerce_tool_args` now wraps non-list, non-null values in a
single-element list when the schema declares `array`. Strings still go
through `_coerce_value` first so JSON-encoded arrays
(`'["a","b"]'`) parse correctly and nullable `"null"` still
becomes `None`. `None` itself is preserved — tools with sensible
defaults already handle it, and we don't want to silently mask a
deliberate null.
Salvaged from NousResearch#19652 (NikolayGusev-astra) — the broader validate-then-
repair layer had several issues (duplicated existing coercion,
mis-classified `old_string` as a path field, prepended non-JSON
prefixes to tool results that break downstream JSON parsing, hardcoded
offset/limit defaults unsuitable for non-read_file tools). The one
genuinely new capability is wrapping bare scalars, which is implemented
here directly inside the existing coercion path.
Co-authored-by: Nikolay Gusev <ngusev@astralinux.ru>
NousResearch#19718) Closes NousResearch#19479. When an orchestrator agent calls kanban_create from a gateway session (e.g. a Telegram user delegating to an orchestrator profile), auto- subscribe the originating (platform, chat, thread, user) to the new task's terminal events. Mirrors the behavior of the /kanban create slash command in gateway/run.py so tool-driven creation is at parity with human-driven creation. Without this, a user who interacts with an orchestrator exclusively via the gateway never receives blocked / completed / gave_up notifications for tasks the orchestrator created on their behalf — silently breaking the gateway-first multi-agent flow the reporter describes. Reads the context-local HERMES_SESSION_* vars via get_session_env() (not os.environ — those are contextvars for asyncio concurrency safety). Falls through cleanly in CLI / cron contexts with no session active (subscribed=False in the response). Best-effort: if the gateway module isn't importable (test rigs stubbing gateway.*), the task still creates, we just skip the subscription. Response gains a 'subscribed' bool so the orchestrator knows whether terminal events will land back in the originating chat or whether it needs to poll / unblock manually. Tests: 4 new in tests/tools/test_kanban_tools.py covering CLI/no-subscribe, telegram/gateway-auto-subscribe, discord-DM/no- thread subscribe, and partial-ctx/no-chat_id no-subscribe. 40/40 kanban tool tests pass.
…sResearch#19718) (NousResearch#19721) Reverts ff3d277. Teknium reviewed the merged PR and decided this behavior isn't wanted — tool-driven kanban_create should not mirror the slash-command path's auto-subscribe. Orchestrators that want their originating chat notified can call kanban_notify-subscribe explicitly; we're not going to make it implicit.
Generic 400 and server-disconnect heuristics used absolute token/message-count fallbacks that are too aggressive for 1M context sessions. Gate those absolute fallbacks to smaller context windows while preserving relative pressure checks. Fixes NousResearch#16351
Keep the configured vision provider when base_url is overridden so credential-pool lookup still resolves provider-specific API keys (e.g. ZAI_API_KEY), and add a regression test for this path.
…compatibility (NousResearch#16478) Gemini's OpenAI-compatibility endpoint strictly requires the `name` field on `role: tool` messages — it returns HTTP 400 ("Request contains an invalid argument") when the function name is missing. OpenAI/Anthropic/ ollama tolerate the absence, so the gap stays invisible until the conversation accumulates a tool turn and the user routes it through Gemini (direct API or via ollama-cloud proxy). Fix: add a `_get_tool_call_name_static()` helper alongside the existing `_get_tool_call_id_static()`, and populate `name` at every site that constructs a `role: tool` message — the pre-call sanitizer stub, the tool-call args repair marker, both interrupt-skip paths, both result-append paths (parallel + sequential), the invalid-tool-name recovery, the invalid-JSON-args recovery, and the exception fallback. Each call site was already in scope of the function name (`function_name`, `skipped_name`, `name`, or a dict tool_call), so the change is local — no new lookups, no behavior change for providers that already worked. Fixes NousResearch#16478
…ives
`updates.backup_keep: 0` (or any negative value) wiped the freshly-
created pre-update zip:
_prune_pre_update_backups(backup_dir, keep=0):
backups = sorted(..., reverse=True) # newest first, includes
# the zip we just wrote
for p in backups[0:]: # = all of them
p.unlink()
The wrapper in `main.py` then printed `Saved: <path>` for a file that
no longer existed (the size lookup is wrapped in `try/except OSError`
which silently degrades to "0 B"), leaving operators believing they had
a recovery point when they had none.
This is a real footgun because some config systems treat 0 as "keep
unlimited"; here it does the opposite — every backup is destroyed
right after creation.
Fix: clamp `keep` to a minimum of 1 inside `_prune_pre_update_backups`
since that helper is only invoked immediately after a fresh backup
is written. Operators who genuinely want no backups should set
`updates.pre_update_backup: false` (which gates creation entirely)
rather than relying on `backup_keep: 0`.
Also extends the `backup_keep` config docstring to spell out the floor
and point at `pre_update_backup: false` as the off-switch.
## Tests
Three regression tests added in `TestPreUpdateBackup`:
- `test_keep_zero_does_not_delete_freshly_created_backup` —
asserts the file persists after `keep=0`
- `test_keep_negative_does_not_delete_freshly_created_backup` —
same for negative values
- `test_keep_zero_still_prunes_older_backups` — proves the floor
only protects the new backup; older ones are still rotated out
Verified the new tests fail on origin/main (without the floor) and
pass with it; full `tests/hermes_cli/test_backup.py` suite green
(84 tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ld session (NousResearch#15909) When a delegation child session (e.g. source='telegram') contains the FTS5 hit but _resolve_to_parent() maps it to a different root session (source='api_server'), the result entry was still reporting the child's source because the loop discarded session_meta as `_` and fell back to match_info.get('source'), which carries the child session's value. Use the resolved parent's session_meta for source, model, and started_at with match_info as a fallback, so the output accurately reflects the session the user actually interacted with. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The heartbeat stale detection was too aggressive: - idle: 5 * 30s = 150s — LLM inference on slow providers (Zhipu/GLM) frequently exceeds 150s, causing heartbeat to stop prematurely - in-tool: 20 * 30s = 600s — borderline for long tool calls When heartbeat stops, parent._last_activity_ts freezes, eventually triggering gateway timeout and killing the entire delegation. New thresholds: - idle: 15 * 30s = 450s — accommodates slow LLM inference - in-tool: 40 * 30s = 1200s — accommodates long-running tool calls child_timeout_seconds (config: delegation.child_timeout_seconds) remains the hard cap for total delegation duration.
- Rebased on upstream/main to resolve conflicts - Added test_run_doctor_accepts_kimi_coding_cn_provider test - All 30 tests pass
…isplay Closes NousResearch#16082. `hermes status` silently omitted four widely-used LLM providers (Google/Gemini, DeepSeek, xAI/Grok, NVIDIA NIM) from the API Keys and API-Key Providers sections. Add them, along with tuple-valued env var support (first found wins) so Google can accept either GOOGLE_API_KEY or GEMINI_API_KEY. Also deduplicates the "NVIDIA" and "NVIDIA NIM" rows that were both pointing at NVIDIA_API_KEY. Salvage of NousResearch#16159 (core behavior preserved + NVIDIA dedup fixup on top of the tuple-support refactor). Co-authored-by: briandevans <252620095+briandevans@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clear inherited provider preference filters when delegation.provider is set so delegated children do not route back to the parent provider. Add a regression test for cross-provider delegation with parent OpenRouter filters. Closes NousResearch#10653
Prevents pre-existing TWILIO_PHONE_NUMBER or SMS_WEBHOOK_URL values in the outer test environment from leaking into the assertion context. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ctions
On VPS/Docker and some Ubuntu 23.10+ hosts, Chromium refuses to start
without --no-sandbox:
- uid=0 (root): hard requirement (VPS/Docker deployments)
- AppArmor apparmor_restrict_unprivileged_userns=1 (Ubuntu 23.10+):
non-root too, under systemd or unprivileged containers
Detect both conditions and inject AGENT_BROWSER_CHROME_FLAGS with
--no-sandbox --disable-dev-shm-usage when the user hasn't already
set the flags themselves.
Salvage of NousResearch#15771 — only the browser_tool.py fix is cherry-picked.
The PR's accompanying MCP preset addition (new feature surface)
was dropped so the bug fix can land independently.
Co-authored-by: ygd58 <buraysandro9@gmail.com>
Steers custom tool creation toward the plugin route by default.
The adding-tools.md guide is now explicitly for built-in core Hermes
tools only.
Key fixes:
- Plugin quickstart: ctx.register_tool() now uses correct keyword-arg
API (name=, toolset=, schema=, handler=) instead of broken 3-arg call
- Handler signature: (params, **kwargs) instead of (params)
- Handler return: json.dumps({...}) instead of plain string
- AGENTS.md: mentions plugin route before built-in tool instructions
- learning-path.md: plugins listed before core tool development
- contributing.md: separates plugin vs core tool paths
Based on PR NousResearch#13138 by @helix4u.
Merged 170+ commits from upstream NousResearch/hermes-agent. Resolved merge conflicts in gateway/run.py, kanban-video-orchestrator docs, scripts/release.py, and tools/vision_tools.py by accepting upstream changes.
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
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.
Daily sync with upstream. Auto-created by cron job.
Commits synced from upstream:
eeb05cf docs: default custom tool creation to plugins
74c1b94 fix(browser): inject --no-sandbox for root and AppArmor userns restrictions
ce22301 test(sms): use clear=True in test_missing_phone_number_is_non_retryable
8308077 fix(delegation): honor provider override for subagents
7a8ee8b fix(gateway): deduplicate Weixin messages by content fingerprint
0b5fd40 fix(delegate): correct _spawn_child → _build_child_agent in comments
42d72b5 fix(status): add missing popular provider API keys to hermes status display
5d6431c fix(doctor): resolve merge conflicts, add kimi-coding-cn test
0e94160 test: add unit tests for heartbeat stale threshold increase
0cc6304 fix(delegation): increase heartbeat stale thresholds
6b4ccb9 fix(session-search): report source from resolved parent, not FTS5 child session (NousResearch#15909)
b46b0c9 fix(backup): floor pre-update backup_keep to 1 so the new backup survives
ef8c213 fix(model-switch): soft-accept unlisted openai-codex models
52882da fix(agent): include name field on every role:tool message for Gemini compatibility (NousResearch#16478)
0443484 fix(qqbot): honor proxy env vars for websocket
6cf7a9e fix(vision): preserve explicit provider auth with custom base_url
b7bbc62 fix(compressor): _prune_old_tool_results boundary direction
d29f90e fix(error_classifier): avoid large-context false overflow heuristics
026a5e4 fix(cli): preserve Windows hidden-dir paths in markdown
3fb3552 revert: auto-subscribe gateway chat on tool-driven kanban_create (NousResearch#19718) (NousResearch#19721)
25b7b0f chore(release): AUTHOR_MAP entries for Tier 1f salvage batch
ff3d277 feat(kanban): auto-subscribe gateway chat on tool-driven kanban_create (NousResearch#19718)
fdf9343 fix(tools): wrap bare scalars in single-element list for array-typed args
6f864f8 fix(redact): add code_file param to skip false-positive ENV/JSON patterns
a175f39 feat(nous): persist Nous OAuth across profiles via shared token store (NousResearch#19712)
69fc6d9 fix(telegram): fall back to document on any send_photo failure, not just dim errors
d3b22b7 fix(kanban): enforce worker task-ownership on destructive tool calls (NousResearch#19713)
1bd5ac7 fix(self-improvement-loop): bump background-review budget to 16 and suppress status leaks (NousResearch#19710)
a79b0ec fix: keep Feishu topic replies from falling back to new threads (local patch)
3ccf723 fix(gateway): read context_length from custom_providers in session info header
8c8f95b fix(gateway): show friendly error when service is not installed
c5789f4 feat(achievements): share card render on unlocked badges (NousResearch#19657)
297eaa3 fix(api_server): emit run.failed when run_conversation returns failed=True
b2b479b docs(kanban): backfill multi-board refs in reference docs (NousResearch#19704)
a8b689f test(kanban): regression for status=running rejection at dashboard PATCH
6b3efce fix(kanban): reject direct status transition to 'running' via dashboard API
652f8e6 fix(test): correct _coerce_number inf/nan test assertions
edf9c75 fix(env): pass -- to cd for hyphen-prefixed workdirs
ae40fca fix(profiles): keep validate_profile_name strict; callers normalize first
a31477d fix(profiles): normalize profile IDs for Kanban assignees and lookups
60c4bc9 fix(security): restore .env/auth.json/state.db with 0600 perms
da8654b fix(dashboard): show custom theme palette swatches
239ea1b fix(image-gen): preserve xAI API error status
75b4a34 fix(cli): check updates against upstream/main for fork users
5ec6baa feat(kanban): multi-project boards — one install, many kanbans (NousResearch#19653)
135b4c8 fix(mcp): decouple AnyUrl import from mcp dependency
0d56362 fix(test): skip bedrock adapter tests when botocore is not installed
d1d2d43 fix(test): add skip marker for transcription tests requiring faster_whisper
844d4a3 chore(release): AUTHOR_MAP entries for Tier 1e salvage batch
110387d docs(open-webui): fill gaps in quick setup — verify curls, ollama flag, restart note (NousResearch#19654)
af6f9bc fix: refresh systemd unit on gateway boot (not just start/restart) (NousResearch#19684)
33f554d feat(kanban-dashboard): workspace kind + path inputs in inline create form (NousResearch#19679)
a219a0a fix(anthropic): strip top-level oneOf/allOf/anyOf from tool input_schema
412f238 fix(google_oauth): close TOCTOU window when saving credentials
e50809b fix(file-tools): cap read_file result size to prevent context window overflow
5b6d413 fix(cli,gateway): surface title errors from /new
f720751 feat(cli,gateway): /new accepts optional session name argument
055fde4 fix(doctor): check global agent-browser when local install not found
e69d11d fix(browser): allow CDP override to pass requirement checks
4607242 fix(model-picker): exclude providers with empty credential pool entries
c8ecb56 fix(cli): reject invalid argv values from -p/--profile before resolving
e3461e0 fix(cli): remove dead 'q' check from quit command resolution
cba86b7 fix(cronjob): treat bare 'custom' provider as unspecified in override
6b88f46 fix(compressor): trigger fallback on timeout errors alongside model-not-found
a45bd28 fix(wecom): set SUPPORTS_MESSAGE_EDITING=False to prevent broken streaming
d2ea959 fix(doctor): skip /models health check for MiniMax CN (returns 404)
d17eff2 fix(delegate): guard _load_config() against delegation: null in config.yaml
2d3d1d9 fix(tui): use --outdir instead of --outfile in hermes-ink build script
145a38a fix(agent): preserve dots in model names for Xiaomi MiMo provider
0896944 fix(cronjob): advertise 'custom:' provider format in tool schema
9c64d09 fix(status): show NVIDIA NIM api key status
64b39d8 chore(release): AUTHOR_MAP entries for Tier 1d salvage batch
20a06c5 fix(dashboard): render null instead of flashing spinner during plugin load
06a6d69 fix(dashboard): defer unknown-route redirect while dashboard plugins load
986ec04 docs: document /kanban slash command (NousResearch#19584)
0628004 docs(model-catalog): rename x-ai/grok-4.20-beta to x-ai/grok-4.20 (NousResearch#19640)
c659a16 fix(cli): detect quoted relative paths in _detect_file_drop
08b8465 fix(email): add required Date header to send_message_tool._send_email
51dc98d fix(agent): detect Qwen3/Ollama inline thinking after tool calls
0df7e61 fix(cli): omit empty api_mode when probing custom models
52c539d fix(agent): disable SDK retries on per-request OpenAI clients
3c070f9 fix(curator): only mark agent-created for background-review sediment (NousResearch#19621)
bff484a fix(kanban-dashboard): widen drawer, bump body fonts, fix code-block contrast (NousResearch#19638)
2a52e28 fix(setup): skip AUXILIARY_VISION_MODEL write when input is blank
7d36533 fix(pty): default TERM for resize probes
99faac2 fix(tui): prevent trailing space in picker-command completions
6da970f fix(tui): close AIAgent on session teardown to prevent FD leak
4e2b20b fix(cli): sync use_gateway in _reconfigure_provider for tts, browser, and web
ba83374 fix(gemini): extract usageMetadata from streaming chunks for token tracking
f6aa196 fix(telegram): fallback to document when photo dimensions exceed limits
ad4542b fix(gateway): allow free_response_channels to override DISCORD_IGNORE_NO_MENTION
54cd633 fix(cron): skip AI call when script produces no output
e224804 fix(cron): drop stale env-var override of persisted provider
d7663c7 fix(docker): exclude compose/profile runtime state from build context
f236cbf fix(tui): declare nanostores dependency
dc63ad0 fix(anthropic): cap max_tokens at 65536 for Qwen models via DashScope
83bbe9b fix(delegation): pass target_model to resolve_runtime_provider in _resolve_delegation_credentials
e2211b2 fix(compressor): reset _summary_failure_cooldown_until in on_session_reset()
3e1559b chore(release): AUTHOR_MAP entries for Tier 1c salvage batch
baf834c chore(release): map cine.dreamer.one@gmail.com to @LeonSGP43
abcaf05 fix(skills): keep manual skills out of curator
cac4f2c test(kanban): update worker-prompt header assertion to match NousResearch#19427
deb59ea fix: allow kanban tools for orchestrator profiles with kanban toolset
9faaa29 fix(delegate): inherit parent fallback_chain in _build_child_agent
cb33c73 fix(run_agent): gate iteration-limit provider routing to OpenRouter
8a364df fix: inherit reasoning config in API server runs
aede94e fix: back up config.yaml before hermes setup modifies it
2c7d7a9 fix(security): bind Meet node server to localhost and restrict token file to owner read
cdde0c8 fix(feishu): enable MEDIA attachment delivery in send_message tool
45fd451 fix: _chromium_installed() now checks AGENT_BROWSER_EXECUTABLE_PATH and system Chrome
c653f5d Clarify session_search auxiliary model docs
8bdec80 fix(agent): surface preflight compression status
d8be50d fix(web): add missing icons for config page category sidebar
0603122 fix(tests): tolerate ps ancestor-walk in find_gateway_pids fallback test (NousResearch#19590)
9c93fc5 fix(tui): call process.exit(0) after Ink exit to trigger terminal cleanup
74c997d fix(gateway): move quick-command dispatch before built-in handlers
c857592 fix(cli): allow custom:* provider slugs in model validation
e8cdcf5 fix: exclude ancestor PIDs from gateway process scan (NousResearch#13242)
8a4fe80 fix(signal): skip reactions for unauthorized senders
e89376d fix(setup): add missing SLACK_HOME_CHANNEL prompt to _setup_slack()
81ce945 fix(gateway): show other profiles in
gateway statusto prevent confusiondf88375 fix: treat ctrl-c as curses cancel
ccb5d87 test: cover max-iterations summary message sanitization
a1cb811 fix(cli): avoid voice TTS restart race
314fe9f chore(release): add AUTHOR_MAP entries for upcoming salvage batch
645b99a test(cron): cover null next_run_at recovery and non-dict origin tolerance
78b635e fix(cron): recover null next_run_at jobs and tolerate non-dict origin
91ea3ae test(skills): add bytes-vs-str equivalence and on-disk hash parity tests
3072e55 skills-hub: hash binary skill bundle files correctly
c90f25d chore(release): map daixin1204@gmail.com to @SimbaKingjoe
744079f fix(curator): prevent false-positive consolidation from substring matching
c030057 fix(kanban): use get_default_hermes_root() in list_profiles_on_disk
1964b05 test(kanban): add failing test for list_profiles_on_disk with custom HERMES_HOME
8163d37 fix(skill): reference built-in video_analyze/vision_analyze tools in kanban-video-orchestrator (NousResearch#19562)
a11aed1 fix(cli): local backend CLI always uses launch directory, stops .env sync of TERMINAL_CWD (NousResearch#19334)
434d70d Merge pull request NousResearch#19540 from NousResearch/single_container_for_all
5671059 feat(docker): launch dashboard as side-process via HERMES_DASHBOARD=1 Adds an optional dashboard side-process to the container entrypoint, toggled by
HERMES_DASHBOARD=1(also acceptstrue/yes). When set, the entrypoint backgroundshermes dashboardbeforeexec-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 (matcheshermes dashboard) - Auto-adds--insecurewhen binding to non-localhost, matching the dashboard's own safety gate for exposing API keys - HERMES_DASHBOARD_TUI is read byhermes dashboarddirectly — no entrypoint plumbing needed Dashboard output is prefixed with[dashboard]viastdbuf+sed -uso it's easy to separate from gateway logs indocker logs. No supervision: if the dashboard crashes it stays down until the container restarts (documented in the:::notepanel). 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 anddepends_on. - Drop the ":::warning" caveat about "Running a dashboard container alongside the gateway is safe" — that case no longer exists.95f3950 Merge pull request NousResearch#19520 from NousResearch/fix_docker_tui
2f2998b fix(tui): tolerate npm's peer-flag drop in lockfile comparison
_tui_need_npm_install()compares the canonicalpackage-lock.jsonagainst the hiddennode_modules/.package-lock.jsonto decide whethernpm installneeds to re-run. npm 9 drops the"peer": truefield 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 runtimenpm 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, sodocker run … hermes-agent --tuiprints: 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_KEYSso 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 everyhermes --tuiinvocation from a normal checkout, silently running a no-opnpm installeach time (it converged because the host'snode_modules/is writable). Startup time on the TUI should drop noticeably.363cc93 fix(cron): bump skill usage when cron jobs load skills
808fee1 fix(auxiliary): propagate explicit_api_key to _try_anthropic()
74636f9 fix(gateway): clear queued reload-skills notes on new/resume/branch
222767e fix: sanitize Telegram help command mentions
6fda92a fix(gateway): bridge top-level require_mention to Telegram config
1bd975c fix(gateway): suppress duplicate voice transcripts
b58db23 fix(kanban): drop worker identity claim from KANBAN_GUIDANCE (NousResearch#19427)
6713274 fix(file): strip leaked terminal fences from reads
2d7543c fix(windows): enforce UTF-8 stdout/stderr to prevent UnicodeEncodeError crash
2ababfe chore(release): map 0xKingBack noreply email
3c42024 fix(curator): pass auxiliary curator api_key/base_url into runtime resolution
3792b77 fix(send_message): support QQBot C2C and group chats
86e64c1 fix(gateway): hide required-arg commands from Telegram menu
408dd8a fix(compressor): skip non-string tool content in dedup pass to prevent AttributeError
5bd9375 fix(vision): guard user_prompt type in video_analyze_tool before debug_call_data construction
6c4aca7 fix(vision): guard user_prompt type before debug_call_data construction
a5cae16 fix(api_server): fall back to default port on malformed API_SERVER_PORT
65bebb9 fix(cli): follow 307 redirects in MiniMax OAuth httpx clients
dfdd7b6 fix(codex-transport): preserve request override headers for xai responses
4a2f822 fix(mcp): reconnect on terminated sessions
2658494 fix(kanban): add per-path env overrides + dispatcher env injection
f5bd77b fix(kanban): anchor board, workspaces, and worker logs at the shared Hermes root
167b564 Revert "fix(cli): CLI/TUI on local backend always uses launch directory, ignores terminal.cwd (NousResearch#19242)" (NousResearch#19329)
9eaddfa fix(cli): CLI/TUI on local backend always uses launch directory, ignores terminal.cwd (NousResearch#19242)
b8ae8cc fix(debug): redact log content at upload time in hermes debug share
c9a3f36 feat: add video_analyze tool for native video understanding (NousResearch#19301)
0dd8e3f rename: video-orchestrator → kanban-video-orchestrator
511add7 feat(skill): add video-orchestrator optional creative skill
e97a999 Merge pull request NousResearch#19307 from NousResearch/bb/fix-terminal-resize-jumble
279b656 fix(tui): clear Apple Terminal resize artifacts
e527240 fix(tools): write_file handler now rejects missing 'content'/'path' args instead of silently writing zero-byte files (NousResearch#19096)
6b4fb9f fix(cron): treat non-dict origin as missing instead of crashing tick
69dd0f7 fix(approval): extend sensitive write target to cover shell RC and credential files
3c59566 chore(release): map leprincep35700 email for PR NousResearch#18440 salvage
b59bb4e fix(gateway): preserve home-channel thread targets across restart notifications
d87fd9f fix(goals): make /goal work in TUI and fix gateway verdict delivery (NousResearch#19209)
55647a5 fix(whatsapp): pin protobufjs >=7.5.5 via npm overrides to clear 3 critical vulns (NousResearch#19204)
6f2dab2 fix: update tests for resume_pending semantics + add AUTHOR_MAP entries
1148c46 fix(gateway): correct ws scheme conversion for https urls
7a22c63 chore: add shellybotmoyer to AUTHOR_MAP
9341034 fix(gateway): send /new response before cancel_session_processing to avoid race (NousResearch#18912)
bf32394 chore: add millerc79 to AUTHOR_MAP
f1e0292 fix(gateway): resume sessions after crash/restart instead of blanket suspend
0a97ce6 chore: add nftpoetrist to AUTHOR_MAP
6c1322b fix(slack): close previous handler in connect() to prevent zombie Socket Mode connections
c14bf44 chore: add 0xyg3n noreply email to AUTHOR_MAP
19ba9e4 fix(gateway/discord): require allowlist auth on slash commands
5d5b891 test: add tests for cmd_key preservation through name clamping
c4c0e5a fix: After _clamp_command_names truncates skill names to fit the 32-cha…
457c7b7 feat(openrouter): add response caching support (NousResearch#19132)
9b5b88b chore: add MottledShadow to AUTHOR_MAP
a22465e fix(weixin): send_weixin_direct cross-loop session check
9987f3d fix(acp): compact Zed tool replay rendering
19854c7 Schedule ACP history replay and fence file output
eb612f5 fix(acp): keep web extract rendering compact
b294d1d fix(acp): keep read-file starts compact
72c8037 fix(acp): polish common tool rendering
ef9a08a fix(acp): polish Zed context and tool rendering
e26f9b2 fix(acp): route Zed thoughts to reasoning callbacks
4f37669 fix(tools): reconfigure enabled unconfigured toolsets
d409a44 fix(model): avoid bedrock credential probe in provider picker