Skip to content

chore: sync with upstream main (2026-05-04)#19

Merged
bot-ted merged 201 commits into
mainfrom
sync/upstream-20260504
May 4, 2026
Merged

chore: sync with upstream main (2026-05-04)#19
bot-ted merged 201 commits into
mainfrom
sync/upstream-20260504

Conversation

@bot-ted

@bot-ted bot-ted commented May 4, 2026

Copy link
Copy Markdown
Owner

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 status to prevent confusion
df88375 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 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.
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 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.
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

helix4u and others added 30 commits May 3, 2026 00:32
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)
QifengKuang and others added 27 commits May 4, 2026 04:54
…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.
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

🚨 CRITICAL Supply Chain Risk Detected

This 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 modified

These files can execute code during package installation or interpreter startup.

Files:

hermes_cli/setup.py

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.

@bot-ted bot-ted merged commit d83f7d2 into main May 4, 2026
1 of 7 checks passed
@bot-ted bot-ted deleted the sync/upstream-20260504 branch May 4, 2026 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.