chore(sync): pull upstream NousResearch/hermes-agent@main#14
Open
github-actions[bot] wants to merge 3229 commits into
Open
chore(sync): pull upstream NousResearch/hermes-agent@main#14github-actions[bot] wants to merge 3229 commits into
github-actions[bot] wants to merge 3229 commits into
Conversation
…c sends The standalone _send_telegram path in send_message_tool lacked the thread-not-found fallback that the gateway adapter has. When a forum topic thread_id was stale or deleted, the send would fail entirely instead of retrying to the General topic. Changes: - Add _is_telegram_thread_not_found() helper matching gateway adapter - Add thread-not-found retry in text send path - Add thread-not-found retry in media send path (with f.seek(0)) - Separate text_kwargs from thread_kwargs to prevent disable_web_page_preview leaking into send_photo/send_video calls Closes NousResearch#27012
…um topics Adds two tests to TestSendTelegramThreadIdMapping: - test_thread_not_found_retries_without_message_thread_id - test_thread_not_found_for_media_retries_without_message_thread_id Refs NousResearch#27012
Three tests covering the NousResearch#27012 fix: - test_is_thread_not_found_matches_expected_errors - test_text_send_retries_without_thread_id_on_thread_not_found - test_disable_web_page_preview_not_leaked_to_media_sends 116/116 existing tests still pass (no regressions).
Register Telegram bot commands across default, private, and group scopes so the slash-command menu is available outside DMs. Changes from review feedback: - Add asyncio.Lock to prevent race condition in _ensure_forum_commands - Extract MAX_COMMANDS_PER_SCOPE constant (30) to avoid magic number - Upgrade error logging from debug->warning in forum registration - Add tests covering lazy forum registration and concurrent safety - Remove /start handler from this PR (separate feature) Fixes review: needs_work (race, magic number, log levels, missing tests)
…LEGRAM_CRON_THREAD_ID When Telegram topic mode is enabled, cron messages delivered to the bot's root DM (TELEGRAM_HOME_CHANNEL without a thread id) land in the system lobby — replies there are rebuffed with the lobby reminder and reply_to_message_id is dropped, so users cannot interact with the cron output (NousResearch#24409). Add an optional TELEGRAM_CRON_THREAD_ID env var that overrides TELEGRAM_HOME_CHANNEL_THREAD_ID for cron deliveries only. Operators can create a "Cron" forum topic in the DM, point this var at its thread id, and replies to cron messages will land in that topic's existing session instead of the lobby. The home-channel thread id (used elsewhere, e.g. restart notifications) is unchanged, and explicit deliver="telegram:chat:thread" targets continue to win over the env var. Per the reporter's clarification on 2026-05-13, option (a) (cron-side route to a dedicated topic + config knob) was chosen. Fixes NousResearch#24409
…ision pipeline When users send images as documents (Telegram file picker), they were rejected with "Unsupported document type" because SUPPORTED_DOCUMENT_TYPES only includes text/office formats. Add SUPPORTED_IMAGE_DOCUMENT_TYPES to base.py and handle them in telegram.py before the document check. - Add SUPPORTED_IMAGE_DOCUMENT_TYPES constant to base.py - Add MIME reverse-lookup for image types in telegram.py - Route image documents through cache_image_from_bytes + vision pipeline - Handle media groups for image documents Closes: NousResearch#20128, NousResearch#18620
When Hermes auto-titles a session in a Telegram DM topic it currently renames the topic itself to the generated title. That works for operator-managed lanes (extra.dm_topics) but is disruptive for ad-hoc Threaded-Mode topics that users name by hand — every first exchange overwrites their chosen title. Add gateway.platforms.telegram.extra.disable_topic_auto_rename (default False, preserving prior behaviour). When set, both _schedule_telegram_topic_title_rename and the underlying _rename_telegram_topic_for_session_title short-circuit before touching the Telegram API. Internal session titles (sessions list, TUI) keep working unchanged. Also bridge the legacy top-level telegram.disable_topic_auto_rename key through to gateway.platforms.telegram.extra so users on the older config layout don't have to migrate to enable it. - Tests cover the runtime flag, the scheduling entry-point, and string truthiness coercion for YAML-loaded values. - Docs updated in messaging/telegram.md with an example block.
…NousResearch#27166) When context compression triggers a mid-turn session split, source.thread_id can be None on synthetic/recovered events. _thread_metadata_for_source then returns None, causing the Telegram adapter to send with no message_thread_id and the response lands in the General thread instead of the active DM topic. Fix: - hermes_state.py: Add get_telegram_topic_binding_by_session() for reverse lookup by session_id (enabled by the existing UNIQUE INDEX on session_id). - gateway/run.py: After session-split detection, if source is a Telegram DM and source.thread_id is None, recover it from the binding via the new method so _thread_metadata_for_source produces the correct thread routing. - tests/: Coverage for the new lookup method and the recovery flow.
…jected When a DM topic lane's message_thread_id is rejected by Telegram (e.g. stale or deleted topic), send_typing now falls back to sending the typing indicator without thread_id so it at least appears in the main DM view, rather than being silently swallowed. Also adds test for the fallback behavior.
The gmail-triage skill's Telegram inline buttons emit callback_data of the form `gt:<verb>:<arg>`, but `_handle_callback_query` had no `gt:` branch — taps fell through silently and the spinner sat there until Telegram timed it out. Add `_handle_gmail_triage_callback`, dispatched from the existing callback router, that: - Authorizes the caller via the same `_is_callback_user_authorized` path as the approval / slash-confirm / clarify handlers. - Maps each verb to a script under `~/.hermes/scripts/gmail-triage/` and runs it async with a 60s timeout. - Splits verbs into one-shots (send / archive / draft / spam) — append the confirmation and strip the keyboard so the action can't fire twice — and sticky-state changes (mute / trust / vip ± -domain) — append the confirmation but leave the keyboard tappable so the user can stack actions on one email. - On failure: toast only, keyboard preserved so the user can retry. - Logs every callback outcome to gateway.log for debugging.
…usResearch#24015) When the active main model has no vision capability — or when the user explicitly configured auxiliary.vision in config.yaml — sending the captured screenshot back to the main model in a multimodal tool-result envelope is the wrong move: it trips HTTP 404 / 400 at the provider boundary (e.g. 'No endpoints found that support image input') and the agent loop reports a hard tool failure for what should have been a simple capture. The reporter on NousResearch#24015 hit this with: model: default: tencent/hy3-preview # no vision support provider: openrouter auxiliary: vision: provider: openrouter model: google/gemini-2.5-flash # explicitly configured …and observed: computer_use(action='capture', mode='som') →⚠️ API call failed (attempt1/3): NotFoundError [HTTP 404] 🔌 Provider: openrouter Model: tencent/hy3-preview 📝 Error: HTTP 404: No endpoints found that support image input Fix: in tools/computer_use/tool.py::_capture_response, after a screenshot is captured (modes 'som' / 'vision'), consult the routing helper introduced earlier in this branch. When it says 'route to aux', materialise the PNG to $HERMES_HOME/cache/vision/, run vision_analyze on it (which honours auxiliary.vision via the standard async_call_llm task='vision' router), and return a text-only JSON tool result that embeds the analysis alongside the existing AX/SOM index. The main model never sees the pixels — it sees an actionable text description plus the same set-of-mark element index it normally uses. The two new helpers (_should_route_through_aux_vision, _route_capture_through_aux_vision) keep the policy and the IO separated so each can be tested in isolation. Both fail open: if the config import fails, if the aux call raises, or if the analysis is empty, we fall back to the existing multimodal envelope so the behaviour is at worst the pre-fix status quo. Temp screenshot files are cleaned up unconditionally in a finally block — even on aux call failure — to avoid leaving residue under cache/vision/. The end-to-end regression for NousResearch#24015 is added in the next commit.
…search#24015) Add tests/tools/test_computer_use_capture_routing.py — 13 integration tests that drive _capture_response end-to-end with deterministic stubs for the routing helper, _run_async, vision_analyze_tool, and get_hermes_dir, so the full code path is exercised without a live cua-driver, real auxiliary client, or network access. Coverage: * TestCaptureResponseDefaultPath (3 cases) - SOM PNG capture returns the legacy multimodal envelope when the routing helper says 'native' (image/png MIME). - Same path returns image/jpeg MIME for JPEG payloads (cua-driver can return either). - AX-only mode never even consults the routing helper because no PNG is present. * TestCaptureResponseRoutedToAuxVision (5 cases) - SOM capture with routing on returns a JSON string with the vision_analysis embedded, the AX/SOM index preserved, and NO image_url parts. Verifies the aux call receives a path under the configured cache and a prompt that grounds itself against the AX summary. - Temp screenshot file is unlinked after _capture_response returns, including when the aux call raises (the finally block runs). - Empty / malformed aux analysis falls back to the multimodal envelope so the user always gets *something* useful. * TestRoutingDecisionWiring (4 cases) - Explicit auxiliary.vision in config flips routing on regardless of main-model vision capability. - Vision-capable main + native tool-result support keeps multimodal. - Config load failure fails open (returns False, multimodal path continues to work). - Helper exception is swallowed and routes to legacy behaviour. * TestBugReproductionAnchor (1 case) - directly pins the NousResearch#24015 contract: when routing is on, the response must NEVER contain a 'data:image' or 'image_url' substring. That is exactly what tripped the reporter's HTTP 404 ('No endpoints found that support image input') on tencent/hy3-preview before the fix. Bug-reproduction proof: $ git checkout upstream/main -- tools/computer_use/tool.py $ scripts/run_tests.sh tests/tools/test_computer_use_capture_routing.py ============================== 13 failed in 1.29s ============================== $ # restore tool.py to this branch's HEAD $ scripts/run_tests.sh tests/tools/test_computer_use_capture_routing.py ============================== 13 passed in 1.04s ============================== Total branch coverage: 85 passed across test_computer_use.py, test_computer_use_vision_routing.py, test_computer_use_capture_routing.py
…-tag fix(ci): stop pushing per-commit SHA tags to Docker Hub
…6681) (NousResearch#30084) * feat(tui): make display.mouse_tracking pick which DEC modes to enable Previously the boolean flag was all-or-nothing across modes 1000+1002+1003+1006. Inside tmux, mode 1003 (any-motion) makes every mouse cross of the prompt row fire a clipboard probe that surfaces as "No image in clipboard" — sometimes dozens in a row. Disabling tracking entirely killed scroll-wheel scrolling too, since tmux's own scrollback is preempted by the alt-screen TUI. `display.mouse_tracking` (and `/mouse <preset>`) now accepts `off | wheel | buttons | all` in addition to the legacy booleans. `wheel` is 1000+1006: scroll wheel + click only, no drag, no hover — the tmux-friendly subset. `buttons` adds 1002 for drag-to-select. `all` (= legacy `true`) keeps the hover-driven UI (scrollbar paginate-on-hover, link mouseenter, etc.). * fix(tui): repaint + sync mouse mode when display.mouse_tracking changes Two interacting bugs left the TUI blank when `display.mouse_tracking` switched at runtime (config edit, /mouse <preset>): 1. AlternateScreen's effect re-runs on every `mouseTracking` change, tearing down and re-entering the alt screen. After re-entry, ink's frame buffers are reset by `resetFramesForAltScreen()` but nothing schedules the follow-up render — the alt screen sits blank until some other state change happens to trigger one. Add a `scheduleRender()` in `setAltScreenActive`'s active=true branch so the freshly-entered alt screen gets a full repaint immediately. 2. `setAltScreenActive` early-returns when `active` hasn't changed, which silently drops a `mouseTracking` change if the cleanup→setup pair somehow leaves `altScreenActive` already true. Call `setAltScreenMouseTracking` explicitly from the AlternateScreen effect so the in-memory mode and terminal DECSET sequence stay in sync regardless of how `setAltScreenActive` resolved (the call is a no-op when the mode is unchanged). * fix(tui): address copilot review #4341269705 - tui_gateway/server.py: drop the never-referenced _MOUSE_TRACKING_MODES frozenset (comment #3284802434). _MOUSE_TRACKING_ALIASES already centralizes the canonical preset set via its values; the separate constant added no behavior. - tests/test_tui_gateway_server.py: update the existing test_config_mouse_uses_documented_key_with_legacy_fallback to assert the new preset strings ('all'/'off' instead of 'on'/'off', display.mouse_tracking persisted as 'all' instead of True) and add test_config_mouse_accepts_preset_strings_and_aliases covering /mouse set with wheel/click/unknown (comment #3284802453). The on/off legacy config.set return shape was an implementation detail of the boolean flag, not a stable API — the slash command, gateway help text, and docs all advertise the preset values now. - ui-tui/packages/hermes-ink/src/ink/ink.tsx: schedule a render at the end of reenterAltScreen() (comment #3284802461). Mirrors the same fix in setAltScreenActive() from ece0a2f — without it, SIGCONT/resize self-heal/stdin-gap re-entry leaves the alt screen blank because every caller returns early after invoking us. * fix(tui): address copilot review #4341308478 round 2 - ui-tui/src/config/env.ts (comment #3284837577): the precedence comment was misleading. Actual behavior on origin/main is HERMES_TUI_MOUSE_TRACKING (explicit override) > Termux default > HERMES_TUI_DISABLE_MOUSE legacy kill-switch. This is preserved from main; the only change here was the wrong comment that claimed DISABLE_MOUSE kept kill-switch semantics. Rewrote the comment block to document the actual precedence ladder. - tui_gateway/server.py /mouse set (comment #3284837607): replaced 'str(value or "").strip().lower()' with the explicit None idiom already used for /indicator, so programmatic callers can pass 0 / False and have them route through _MOUSE_TRACKING_ALIASES → 'off' instead of collapsing to '' and triggering the toggle path. - ui-tui/packages/hermes-ink/src/ink/components/AlternateScreen.tsx (comment #3284837620): always prepend DISABLE_MOUSE_TRACKING before enableMouseTrackingFor(...) on mount. Otherwise selecting 'wheel'/'buttons' from a state where DEC 1003 was already asserted (crash, another app, debugger) would silently leave hover on. Also unconditionally DISABLE on unmount so a crash mid-mount can't leak DEC modes back to the host shell. * chore(release): map [email protected] to @nthrow for NousResearch#26681 salvage * fix(tui): drop redundant setAltScreenMouseTracking in AlternateScreen Copilot review #4341356637 (comment #3284880417). The explicit setAltScreenMouseTracking(mouseTracking) after setAltScreenActive(true, mouseTracking) was defensive paranoia added in the previous fix commit that's not actually reachable in practice: - React's cleanup always runs before the next setup, so on any prop change (mouseTracking or writeRaw) the cleanup sets active=false first. Setup then sees active was false and applies the new mode via setAltScreenActive without early-returning. - On the impossible 'active stayed true' path, the writeRaw above has already sent DISABLE_MOUSE_TRACKING + enableMouseTrackingFor(newMode) to the terminal, so the in-memory mode would lag but the visible state is already correct. Removing the redundant call means a single DEC sequence per mount. If the 'active stayed true' path ever manifests in practice, the right fix is in setAltScreenActive (track mode regardless of the active early-return), not here. * fix(tui): always DISABLE before enableMouseTrackingFor in ink.tsx Copilot review #4341379994 (comments #3284900825, #3284900840, #3284900852). Three remaining call sites in ink.tsx still re-enabled mouse tracking without first sending DISABLE_MOUSE_TRACKING: - handleResize alt-screen recovery (line ~577) - reassertTerminalModes stdin-gap re-assertion (line ~1351) - reenterAltScreen SIGCONT/resize/stdin-gap self-heal (line ~1408) For 'wheel'/'buttons' presets, omitting DISABLE leaves any externally- asserted DEC 1003 (other apps, prior crash, tmux state) still active and the hover-free preset silently has hover on. DISABLE_MOUSE_TRACKING is idempotent and safe to send unconditionally — it resets all four modes. Matches the pattern already in setAltScreenMouseTracking and the AlternateScreen mount path. * fix(tui): always DISABLE before enableMouseTrackingFor in exitAlternateScreen Copilot review #4341452823 (comment #3284959762). exitAlternateScreen() was the last call site in ink.tsx still re-enabling mouse tracking without DISABLE first. Editors (vim/nvim/less) and tmux can leave DEC 1003 hover asserted across the handoff back; without DISABLE, 'wheel'/'buttons' presets silently kept hover on after the editor quit. Now all five enableMouseTrackingFor() call sites in ink.tsx prepend DISABLE_MOUSE_TRACKING — handleResize, reassertTerminalModes, reenterAltScreen, setAltScreenMouseTracking, exitAlternateScreen. * fix(tui): add defensive default to enableMouseTrackingFor switch Copilot review #4341485231 (comment #3284979323). TS exhaustive switch returns string per the type system, but a JS caller / corrupted config / hot-reload-in-dev could reach the function with an unknown value at runtime. Without a default, that path returns undefined which then concatenates as the literal string 'undefined' into the terminal byte stream — visibly garbling output. Treat unknown as 'off' (no DEC sequences) so the worst case is silent input loss rather than a wrecked screen. --------- Co-authored-by: Nat Thrower <[email protected]>
…0162) Keep the visible transcript mounted after /branch switches to the new session, since the backend already carries the copied history forward.
…ousResearch#22865) `computer_use(action='capture', mode='ax')` returned the full AX element list verbatim in the JSON response. Dense Electron / Obsidian / JetBrains UIs publish 500+ AX nodes (one reproduction in NousResearch#22865 returned 597 elements against Obsidian), so a single capture could consume enough context to trigger compression failures or render the session unusable. The human-readable `_format_elements` summary is already capped at 40 lines, so the truncation gap was invisible to anyone reading the summary output. Add a `max_elements` argument to the tool schema, default 100, that trims the AX `elements` array. When the cap fires, the response surfaces `total_elements` and `truncated_elements` and appends a "raise max_elements or pass app= to narrow" hint to the summary so the model knows the JSON view is partial and can re-issue with a tighter scope. Validation is centralized in `_coerce_max_elements`: missing / non-integer / sub-1 inputs fall back to the default cap, so the protection can never be silently disabled by a malformed tool-call argument. The cap only affects AX-mode JSON; `mode='som'` and `mode='vision'` keep returning a screenshot + image-aware summary unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Four findings from Copilot's review on PR NousResearch#22891, all in the AX elements-array cap added by 22fa1ed: 1. The truncation note ("response truncated to N of M elements") was appended unconditionally — including in the som/vision multimodal path, whose response carries a screenshot rather than an `elements` array. The note described a payload field that wasn't present. Moved the note into the AX-text branch where the array actually appears. 2. `_format_elements(cap.elements)` ran on the full untrimmed list with its own `max_lines=40` cap, so a caller passing `max_elements=10` would see summary lines referencing `#11..NousResearch#40` even though the JSON `elements` array only held #1..#10. Format on `visible_elements` instead so the summary indices always exist in the response. 3. `_coerce_max_elements` enforced a lower bound but no upper bound, so `max_elements=10_000_000` silently disabled the safeguard and reintroduced the original context-blow-up. Added a hard cap (`_MAX_ALLOWED_MAX_ELEMENTS = 1000`) that clamps oversized values. 4. The schema string said "Default 100" but the property carried no `default` field, and claimed `max_elements` had no effect on som/ vision while the image-missing fallback path can still return an elements array. Added `"default": 100`, `"maximum": 1000`, and clarified the fallback-path wording. Each finding gets a regression test: - test_capture_ax_clamps_oversized_max_elements_to_hard_cap - test_capture_ax_summary_indices_match_returned_elements - test_capture_multimodal_summary_omits_truncation_note - test_schema_max_elements_documents_default_and_upper_bound Verified with `pytest tests/tools/test_computer_use.py` (53 passed, including the 5 new cases). Confirmed each new test fails on the pre-fix code path before applying the production change. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The cherry-pick of NousResearch#22891 (max_elements cap) reshuffled _capture_response so summary was assigned inside both the multimodal and AX branches, but NousResearch#30126's aux-vision routing call (_route_capture_through_aux_vision) fires BEFORE either branch and references the not-yet-bound name. Compute summary once up-front, keep the AX-branch rebuild for the truncation note.
Salvaged from NousResearch#28942 (adybag14-cyber). Only the Ink TUI half is taken here — the bundled "termux compatibility note" added to skills_tool.py in the original PR did not address the actual user-reported bug (skill_matches_platform() filtering Linux skills out on Termux) and also regressed the EXCLUDED_SKILL_DIRS set used to prune nested .venv/site-packages skills. Changes: - ui-tui/src/lib/prompt.ts: single-cell ASCII '>' marker in Termux mode to avoid ambiguous-width glyph artifacts while typing. - ui-tui/src/components/appLayout.tsx: suppress profile prefix on narrow Termux panes (>=90 cols still shows it). - ui-tui/src/lib/inputMetrics.ts + components/messageLine.tsx + lib/virtualHeights.ts: termux-aware transcript body width — drop the desktop 20-col floor on narrow mobile layouts, align virtual heights with actual rendered width. - ui-tui/src/components/textInput.tsx: disable fast-echo bypass by default in Termux to avoid ghosting at soft-wrap boundaries. HERMES_TUI_TERMUX_FAST_ECHO=1 opts back in. Tests: ui-tui/src/__tests__/{prompt,termuxComposerLayout,textInputFastEcho}.test.ts (12 PR-added tests pass; 3 pre-existing wrapAnsi-bundling failures on main are unrelated.) The real skill-listing fix on Termux ('android' platform matching Linux skills) ships as a follow-up commit on this branch.
Reported by @LikiusInik in Discord: on Termux only 3 built-in skills
appeared and /gh-pr-workflow + every other slash-skill from
github/productivity/mlops was missing.
Root cause: skill_matches_platform() compares sys.platform.startswith()
against the skill's platforms list. Termux is a Linux userland on
Android, but Python 3.13+ reports sys.platform == "android" instead of
"linux" — so the ~60 built-in skills tagged platforms:[linux,macos,
windows] (github-pr-workflow, google-workspace, github-auth,
huggingface-hub, etc.) all got filtered out at the listing step in
tools/skills_tool.py:_find_all_skills and never appeared as /slash
commands or in skill_view.
Fix: when is_termux() detects we're running inside Termux, accept
"linux" platform tags regardless of whether sys.platform is "linux"
(pre-3.13) or "android" (3.13+). Also accept explicit
platforms:[termux] / [android] tags. macOS-only and Windows-only
skills correctly remain excluded.
E2E (simulated TERMUX_VERSION=set + sys.platform="android"):
Before: _find_all_skills() returned ~3 skills.
After: _find_all_skills() returns 84 skills including
github-pr-workflow, google-workspace, github-auth,
huggingface-hub. Apple-only skills remain excluded.
Non-Termux Linux/macOS/Windows behavior unchanged (verified).
Tests: tests/agent/test_skill_utils.py — 9 new cases covering
android-as-Termux, the [linux,macos,windows] case, macOS-only
exclusion, explicit termux/android tags, non-Termux Android safety,
and unchanged behavior on real Linux/macOS.
The upstream cua-driver installer resolves the latest release and attempts to download an architecture-specific asset. When the release only ships arm64 builds (as of v0.1.6), the installer fails with a raw 404 on Intel macOS with no clear path forward. Add _check_cua_driver_asset_for_arch() that probes the GitHub Releases API before running the installer. If the latest release has no x86_64/amd64 asset, print a clear warning and link to the upstream issue. On arm64 or API failure, fail open and let the installer proceed as before. Fixes NousResearch#24530
* fix(tui): surface verbose tool details Emit redacted structured verbose args/results to the TUI so /verbose verbose can show full tool detail without reopening stdout, and fail closed if redaction is unavailable. Salvages NousResearch#29011. Co-authored-by: helix4u <[email protected]> * fix(tui): address verbose detail review Label verbose tool failures as errors, cover forced verbose reasoning, and avoid new diff type warnings from the redaction regression tests. * fix(tui): bound verbose tool payloads Cap verbose tool detail text before emitting JSON-RPC events and preserve verbose results on inline diff completions. * fix(tui): align termux argv test with gc flag Update the stale TUI launch expectation so the Termux freshness path matches the current direct Node argv. --------- Co-authored-by: helix4u <[email protected]>
…rch#5544) MemoryManager.get_all_tool_schemas() output was appended to AIAgent.tools unconditionally — bypassing the enabled_toolsets / platform_toolsets filter. Setting `platform_toolsets: telegram: []` had no effect: fact_store and other memory provider tools still leaked into the tool surface on every session. Impact on local models (per @thundercat49's benchmarks on Qwen3-30B-A3B Q4_K_M / RTX 3090): tool-formatted prompts process at 134 tok/s vs 1,230 tok/s for plain text. With 8 memory tool schemas injected, a simple 'hello' on Telegram took ~42s instead of ~1.7s. Small models also entered tool-call loops when memory tools were the only tools present. Gate condition (matches the natural meaning of enabled_toolsets): None → no filter, inject (backward compat) contains 'memory' → user opted in, inject otherwise (including []) → skip injection Co-authored-by: Teknium <[email protected]>
…5544 sibling) The memory-provider gate added in the prior commit closes one of two blind-injection sites in agent_init.py. The context engine block (lines ~1445) follows the identical pattern: agent.context_compressor.get_tool_schemas() (lcm_grep, lcm_describe, lcm_expand) was appended to agent.tools unconditionally, ignoring enabled_toolsets. Same bug class, same local-model latency penalty, same one-line gate — using 'context_engine' as the toolset name (matches the existing plugin-system convention in plugins.py, plugins_cmd.py, etc.). Also adds Lempkey to scripts/release.py AUTHOR_MAP for the prior commit's authorship.
The ensure('stt.faster_whisper') lazy-install mechanism was defined in
lazy_deps.py but never called from the STT code path. When
_HAS_FASTER_WHISPER (a module-level constant) evaluated to False at
import time, _get_provider() returned 'none' immediately without
attempting installation. On fresh container builds or venv recreations,
this meant voice message transcription broke silently until someone
manually installed faster-whisper.
Add _try_lazy_install_stt() helper that calls ensure() and
re-checks dynamically via importlib.util.find_spec. Wire it into
all three gates in transcription_tools.py:
- _get_provider() explicit 'local' path (line 221)
- _get_provider() auto-detect path (line 287)
- _transcribe_local() guard (line 405)
This ensures the first voice message after any fresh install triggers
auto-installation instead of failing permanently until a process restart.
Co-authored-by: CipherFrame <[email protected]>
…ousResearch#27344) (NousResearch#30259) Some providers (Xiaomi MiMo, some Alibaba endpoints, a long tail of OpenAI-compatible servers) follow the OpenAI spec strictly and require tool message `content` to be a string — they reject our list-type content (text + image_url parts) with HTTP 400 'text is not set' / 'tool message content must be a string'. Instead of an allowlist of known-good providers (maintenance burden, guaranteed to miss aggregators like OpenRouter where the underlying model determines support, not the aggregator name), this lands a reactive recovery: 1. New `FailoverReason.multimodal_tool_content_unsupported` with a small pattern list covering the common 400 wordings. 2. `AIAgent._try_strip_image_parts_from_tool_messages` walks the API message list, downgrades any `role:tool` message whose content is list-with-image to a plain text summary (preserves text parts) in place, AND records the active (provider, model) in a session-scoped `_no_list_tool_content_models` set. 3. `_tool_result_content_for_active_model` short-circuits to a text summary when (provider, model) is in the cache — so after the first 400 + retry, subsequent screenshots in the same session skip the round trip entirely. 4. Retry hook in `agent.conversation_loop` mirrors the existing `image_too_large` recovery: detect the reason, run the helper, retry once, fall through to the normal error path if no list-type tool content was actually present. Cache is transient (per-session) by design — next session retries in case the provider added support, no persistent state to maintain. Fixes NousResearch#27344. Closes NousResearch#27351 (allowlist approach superseded by reactive recovery).
curses.init_pair(N, 8, -1) uses extended color 8 ("bright black" /
dim gray) which does not exist on 8-color terminals (COLORS == 8,
valid range 0-7). This crashes the entire plugins UI, session
browser, and radio picker in Docker containers with:
curses.error: init_pair() : color number is greater than COLORS-1
Replace all 5 occurrences across plugins_cmd.py, main.py, and
curses_ui.py with min(8, curses.COLORS - 1), which falls back to
COLOR_WHITE (7) on 8-color terminals.
Closes NousResearch#13688
…ackend stub _dispatch() routes action="set_value" to backend.set_value(), but: - ComputerUseBackend did not declare set_value as @AbstractMethod, so subclasses could silently omit it without a TypeError at class load time. - _NoopBackend (the test/CI stub) had no set_value method at all, causing AttributeError in any test that exercises the set_value action path. Fix: - Add set_value as @AbstractMethod to ComputerUseBackend in backend.py. - Add a recording stub in _NoopBackend in tool.py. - Add two TestDispatch cases: one verifying the call reaches the backend, one verifying the missing-value guard returns a clean error.
_maybe_follow_capture() issued a follow-up screenshot unconditionally when capture_after=True, even when res.ok=False. The model then received a normal-looking screenshot alongside an error message, and in practice it often ignored ok=False and proceeded as if the action had succeeded. Fix: return _text_response(res) early when res.ok is False so the model receives only the error and can decide how to recover. Tests added: - test_capture_after_skipped_when_action_failed: patches click to return ok=False and asserts no capture call is issued. - test_capture_after_fires_when_action_succeeds: ensures the happy path still triggers the follow-up capture.
…ion messages _tool_remember and on_memory_write were posting memories as session messages that depend on commit-time VLM extraction to persist. With extraction_enabled: false (no VLM configured), the extraction pipeline never processes these messages, causing memories to be silently lost. Replace both paths with direct POST to /api/v1/content/write?mode=create, which creates the file, stores the content, and queues vector indexing in a single API call. Error reporting is immediate — no silent failures. - Maps viking_remember category to viking:// subdirectory - Generates UUID-based URIs via uuid4().hex[:12] - Returns byte count in confirmation message
…, dedupe URI builder - on_memory_write: map target='memory' -> patterns/, 'user' -> preferences/ (was hardcoded to preferences/ for both) - Replace client._user with self._user (no private-attr leakage) - Extract _build_memory_uri() helper + module-level subdir maps - Restore on_memory_write signature parity with MemoryProvider base (metadata kwarg; eliminates Pyright incompatible-override warning) - AUTHOR_MAP entry for [email protected]
Owner
|
Auto-merge bot: skipping — has conflicts. Manual rebase needed. Generated by Claude Code |
1 similar comment
Owner
|
Auto-merge bot: skipping — has conflicts. Manual rebase needed. Generated by Claude Code |
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.
Automated sync from
NousResearch/hermes-agent@main.d617858896788aaddce77746e3bd890b3e75124cReview the diff and let CI pass before merging. If the fork has zero local commits ahead, this is effectively a fast-forward and can be merged once green.
Generated by
.github/workflows/sync-upstream.yml.