From 35f9de762495e3e8428cc17d127c52ceec06876c Mon Sep 17 00:00:00 2001 From: shellybotmoyer <258858106+shellybotmoyer@users.noreply.github.com> Date: Sat, 16 May 2026 22:51:17 -0700 Subject: [PATCH 01/11] fix(kanban): --severity filter uses >= comparison per documented behavior (#26379) --- hermes_cli/kanban.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hermes_cli/kanban.py b/hermes_cli/kanban.py index 76f95db4facd3..b4024e2e70e12 100644 --- a/hermes_cli/kanban.py +++ b/hermes_cli/kanban.py @@ -1403,7 +1403,7 @@ def _cmd_diagnostics(args: argparse.Namespace) -> int: sev = getattr(args, "severity", None) if sev: for tid in list(diags_by_task.keys()): - kept = [d for d in diags_by_task[tid] if d.severity == sev] + kept = [d for d in diags_by_task[tid] if kd.SEVERITY_ORDER.index(d.severity) >= kd.SEVERITY_ORDER.index(sev)] if kept: diags_by_task[tid] = kept else: From 4f449d9d32563f749de156b03337501f475f8fd9 Mon Sep 17 00:00:00 2001 From: austrian_guy <33156212+ether-btc@users.noreply.github.com> Date: Sat, 16 May 2026 00:56:33 +0200 Subject: [PATCH 02/11] fix(run_agent): guard memory provider init against empty/whitespace string --- run_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_agent.py b/run_agent.py index b10a68cf9d09b..41af78f16c311 100644 --- a/run_agent.py +++ b/run_agent.py @@ -2061,7 +2061,7 @@ def __init__( try: _mem_provider_name = mem_config.get("provider", "") if mem_config else "" - if _mem_provider_name: + if _mem_provider_name and _mem_provider_name.strip(): from agent.memory_manager import MemoryManager as _MemoryManager from plugins.memory import load_memory_provider as _load_mem self._memory_manager = _MemoryManager() From 39051fe32f67be600b634161f6479a427ce05bdc Mon Sep 17 00:00:00 2001 From: LifeJiggy Date: Fri, 15 May 2026 19:39:29 +0100 Subject: [PATCH 03/11] fix: add paste collapse logging to aid debugging Adds logger.info when large pastes are collapsed to file references in both paste-code paths (handle_paste and _on_text_changed). Logs paste ID, line count, character count, and file path so operators can correlate missing- content reports with specific paste files. This is a diagnostic aid, not a fix for the paste-drop issue. --- cli.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli.py b/cli.py index c1ba1c0ddd2ef..42b1482578e37 100644 --- a/cli.py +++ b/cli.py @@ -12604,6 +12604,7 @@ def handle_paste(event): paste_dir.mkdir(parents=True, exist_ok=True) paste_file = paste_dir / f"paste_{_paste_counter[0]}_{datetime.now().strftime('%H%M%S')}.txt" paste_file.write_text(pasted_text, encoding="utf-8") + logger.info("Collapsed paste #%d: %d lines, %d chars -> %s", _paste_counter[0], line_count + 1, len(pasted_text), paste_file) placeholder = f"[Pasted text #{_paste_counter[0]}: {line_count + 1} lines \u2192 {paste_file}]" prefix = "" if buf.cursor_position > 0 and buf.text[buf.cursor_position - 1] != '\n': @@ -12771,6 +12772,7 @@ def _on_text_changed(buf): paste_dir.mkdir(parents=True, exist_ok=True) paste_file = paste_dir / f"paste_{_paste_counter[0]}_{datetime.now().strftime('%H%M%S')}.txt" paste_file.write_text(text, encoding="utf-8") + logger.info("Collapsed paste #%d: %d lines, %d chars -> %s (fallback)", _paste_counter[0], line_count + 1, len(text), paste_file) _paste_just_collapsed[0] = True buf.text = f"[Pasted text #{_paste_counter[0]}: {line_count + 1} lines \u2192 {paste_file}]" buf.cursor_position = len(buf.text) From 116d92e88c1c9024aa4f61eb5badf662ef5e70a0 Mon Sep 17 00:00:00 2001 From: nekwo Date: Fri, 15 May 2026 12:41:10 -0400 Subject: [PATCH 04/11] fix(windows): make PowerShell installer parse in 5.1 --- scripts/install.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 5ed7aa755fd33..53a1ea9648650 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -1,4 +1,4 @@ -# ============================================================================ +# ============================================================================ # Hermes Agent Installer for Windows # ============================================================================ # Installation script for Windows (PowerShell). From 55a53a0c5d799be591dfd0fcb3a565f8b07a8354 Mon Sep 17 00:00:00 2001 From: flooryyyy <67979730+flooryyyy@users.noreply.github.com> Date: Fri, 15 May 2026 15:01:32 +0100 Subject: [PATCH 05/11] fix(delegate): tool_trace false-positive error detection for short outputs --- tools/delegate_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/delegate_tool.py b/tools/delegate_tool.py index 136ea63ac40fb..e9ad32e0d3a73 100644 --- a/tools/delegate_tool.py +++ b/tools/delegate_tool.py @@ -1649,7 +1649,7 @@ def _run_with_thread_capture(): trace_by_id[tc_id] = entry_t elif msg.get("role") == "tool": content = msg.get("content", "") - is_error = bool(content and "error" in content[:80].lower()) + is_error = _looks_like_error_output(content) result_meta = { "result_bytes": len(content), "status": "error" if is_error else "ok", From f64028429dd1b2c4fc0349b00d2c6ca52a8c2c0b Mon Sep 17 00:00:00 2001 From: dgians <188585318+dgians@users.noreply.github.com> Date: Sat, 16 May 2026 22:51:17 -0700 Subject: [PATCH 06/11] feat(gateway): add .ts/.py/.sh to SUPPORTED_DOCUMENT_TYPES The gateway already accepts plain-text config files (.ini, .cfg) and structured formats (.json, .yaml, .toml) as documents, but not common source-file extensions. Sending a .ts/.py/.sh file currently requires renaming it to .txt first. Adds .ts, .py, .sh as text/plain, consistent with the existing .ini/.cfg entries. Co-Authored-By: Claude Opus 4.7 (1M context) --- gateway/platforms/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gateway/platforms/base.py b/gateway/platforms/base.py index c6bdc38c3b920..7b3147e21f45c 100644 --- a/gateway/platforms/base.py +++ b/gateway/platforms/base.py @@ -829,6 +829,9 @@ def cache_video_from_bytes(data: bytes, ext: str = ".mp4") -> str: ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ".ts": "text/plain", + ".py": "text/plain", + ".sh": "text/plain", } From 6bf747c3018b8a569c4283bf91a10b5982824a77 Mon Sep 17 00:00:00 2001 From: shellybotmoyer <258858106+shellybotmoyer@users.noreply.github.com> Date: Sat, 16 May 2026 22:51:17 -0700 Subject: [PATCH 07/11] fix(credential_pool): parse ISO-string last_status_at during from_dict rehydration (#25516) --- agent/credential_pool.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/agent/credential_pool.py b/agent/credential_pool.py index 504742145c1b1..7f27873a7fb88 100644 --- a/agent/credential_pool.py +++ b/agent/credential_pool.py @@ -129,6 +129,9 @@ def __getattr__(self, name: str): def from_dict(cls, provider: str, payload: Dict[str, Any]) -> "PooledCredential": field_names = {f.name for f in fields(cls) if f.name != "provider"} data = {k: payload.get(k) for k in field_names if k in payload} + # Rehydrated last_status_at may be an ISO string from to_dict() — normalize to float epoch + if "last_status_at" in data and isinstance(data["last_status_at"], str): + data["last_status_at"] = _parse_absolute_timestamp(data["last_status_at"]) extra = {k: payload[k] for k in _EXTRA_KEYS if k in payload and payload[k] is not None} data["extra"] = extra data.setdefault("id", uuid.uuid4().hex[:6]) From 3af4944465365fe88cca591f50d9a5ceb40328e0 Mon Sep 17 00:00:00 2001 From: flanny7 Date: Sun, 17 May 2026 02:41:29 +0900 Subject: [PATCH 08/11] fix(install): use resolved python variable in setup_open_webui.sh The install_open_webui function correctly resolved the python interpreter into the $py variable, but hardcoded 'python' in subsequent pip install commands. This caused 'command not found' or 'externally-managed-environment' errors on systems where 'python' is not implicitly aliased to 'python3'. --- scripts/setup_open_webui.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/setup_open_webui.sh b/scripts/setup_open_webui.sh index 0cca44ddd717d..9975c911f3f9e 100755 --- a/scripts/setup_open_webui.sh +++ b/scripts/setup_open_webui.sh @@ -163,8 +163,8 @@ install_open_webui() { "$py" -m venv "$OPEN_WEBUI_VENV" # shellcheck disable=SC1090 source "$OPEN_WEBUI_VENV/bin/activate" - python -m pip install --upgrade pip setuptools wheel - python -m pip install open-webui + "$py" -m pip install --upgrade pip setuptools wheel + "$py" -m pip install open-webui } write_launcher() { From b905985dcbd7aa07d4408570563a31890dff5786 Mon Sep 17 00:00:00 2001 From: hermesagent26 <276067471+hermesagent26@users.noreply.github.com> Date: Sat, 16 May 2026 22:51:17 -0700 Subject: [PATCH 09/11] fix(run_agent): detect kimi models via model name for reasoning pad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit previously only checked provider ID and base URL. When kimi-k2.6 is served via ollama-cloud (or any third-party provider), provider is not 'kimi-coding' and base URL is not api.kimi.com — so reasoning_content pad was never injected. This caused HTTP 400 from Ollama Cloud's Go backend: 'invalid message content type: map[string]interface {}'. Fix: add model-name detection ('kimi' in model.lower()) so any route serving a kimi model gets the required reasoning_content echo-back. Refs the 400/401 Telegram errors where kimi-k2.6 via ollama-cloud consistently failed after tool-call turns. --- run_agent.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/run_agent.py b/run_agent.py index 41af78f16c311..b239f2aeb609d 100644 --- a/run_agent.py +++ b/run_agent.py @@ -10437,12 +10437,16 @@ def _needs_kimi_tool_reasoning(self) -> bool: Kimi ``/coding`` and Moonshot thinking mode both require ``reasoning_content`` on every assistant tool-call message; omitting it causes the next replay to fail with HTTP 400. + + Also detects Kimi models served through third-party providers (e.g. + ollama-cloud) by matching ``kimi`` in the model name. """ return ( self.provider in {"kimi-coding", "kimi-coding-cn"} or base_url_host_matches(self.base_url, "api.kimi.com") or base_url_host_matches(self.base_url, "moonshot.ai") or base_url_host_matches(self.base_url, "moonshot.cn") + or "kimi" in (self.model or "").lower() ) def _needs_deepseek_tool_reasoning(self) -> bool: From e9d9ddd8e0d9bddcfa70600850de75c1e914a8ac Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sat, 16 May 2026 22:52:28 -0700 Subject: [PATCH 10/11] fix(deepseek): set default_aux_model on profile so aux warning stops firing Closes #26924 (and supersedes #26926) in spirit. DeepSeek was missing `default_aux_model` on its `ProviderProfile`, so `_get_aux_model_for_provider("deepseek")` returned an empty string and the compression / vision / session-search paths emitted "No auxiliary LLM provider configured -- context compression will drop middle turns without a summary." on every DeepSeek session, even when the user had perfectly working DeepSeek credentials. Fix lands at the profile layer rather than the legacy `_API_KEY_PROVIDER_AUX_MODELS_FALLBACK` dict the original PR targeted. Every modern provider (gemini, zai, minimax, anthropic, kimi-coding, stepfun, ollama-cloud, gmi, novita, kilocode, ai-gateway, opencode-zen) sets `default_aux_model` on its `ProviderProfile`; the fallback dict only exists for providers that predate the profiles system. Tests added under `tests/plugins/model_providers/test_deepseek_profile.py`: - `test_profile_advertises_deepseek_chat` -- pins the profile attribute - `test_consumer_api_returns_deepseek_chat` -- pins the consumer API behavior - `test_consumer_api_returns_non_empty` -- regression guard for the symptom in the issue Original diagnosis and aux-model choice from @kriscolab in PR #26926; moved one layer up. Co-authored-by: kriscolab <71590782+kriscolab@users.noreply.github.com> --- plugins/model-providers/deepseek/__init__.py | 1 + .../model_providers/test_deepseek_profile.py | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/plugins/model-providers/deepseek/__init__.py b/plugins/model-providers/deepseek/__init__.py index f67146df113cb..525766f87eb62 100644 --- a/plugins/model-providers/deepseek/__init__.py +++ b/plugins/model-providers/deepseek/__init__.py @@ -94,6 +94,7 @@ def build_api_kwargs_extras( "deepseek-reasoner", ), base_url="https://api.deepseek.com/v1", + default_aux_model="deepseek-chat", ) register_provider(deepseek) diff --git a/tests/plugins/model_providers/test_deepseek_profile.py b/tests/plugins/model_providers/test_deepseek_profile.py index c53e70070a810..8c316a38086f9 100644 --- a/tests/plugins/model_providers/test_deepseek_profile.py +++ b/tests/plugins/model_providers/test_deepseek_profile.py @@ -182,3 +182,26 @@ def test_v3_chat_full_kwargs_omit_thinking(self, deepseek_profile): ) assert "reasoning_effort" not in kwargs assert "extra_body" not in kwargs or "thinking" not in kwargs.get("extra_body", {}) + + +class TestDeepSeekAuxModel: + """DeepSeek aux model is set on the profile so users stop seeing the + bogus 'No auxiliary LLM provider configured' warning (#26924). + + Pinned at the profile layer rather than the legacy + `_API_KEY_PROVIDER_AUX_MODELS_FALLBACK` dict — new providers are + expected to set `default_aux_model` on `ProviderProfile`, and the + fallback dict only exists for providers that predate the profiles + system. + """ + + def test_profile_advertises_deepseek_chat(self, deepseek_profile): + assert deepseek_profile.default_aux_model == "deepseek-chat" + + def test_consumer_api_returns_deepseek_chat(self): + from agent.auxiliary_client import _get_aux_model_for_provider + assert _get_aux_model_for_provider("deepseek") == "deepseek-chat" + + def test_consumer_api_returns_non_empty(self): + from agent.auxiliary_client import _get_aux_model_for_provider + assert _get_aux_model_for_provider("deepseek") != "" From 59236dc07a9a97cf73abd068a0cd04b2807a620d Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sat, 16 May 2026 22:53:29 -0700 Subject: [PATCH 11/11] chore(release): AUTHOR_MAP entries for batch salvage group 2 contributors Adds release-note attribution mappings for 10 contributors from the low-hanging-fruit salvage group 2 batch: - @shellybotmoyer (PR #26661, #25576) - @ether-btc (PR #26632) - @LifeJiggy (PR #26516) - @nekwo (PR #26481) - @flooryyyy (PR #26374) - @dgians (PR #26034, incl. zealy-tzco bot-committer alias) - @flanny7 (PR #27030) - @hermesagent26 (PR #26438) - @kriscolab (PR #26926, co-author on salvage commit) --- scripts/release.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/release.py b/scripts/release.py index 07952b63c0e53..6bbc2ad4ae377 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -1096,6 +1096,23 @@ "4296245+matthewlai@users.noreply.github.com": "matthewlai", "109617724+0xchainer@users.noreply.github.com": "0xchainer", # PR #27154/27138/27147 salvage "201800237+kronexoi@users.noreply.github.com": "kronexoi", # PR #27167 salvage (Teams port fallback) + # batch salvage (May 2026 LHF run, group 2) + "shellybotmoyer@example.com": "shellybotmoyer", # PR #26661 (kanban --severity >=) + "coulson@shellybotmoyer.com": "shellybotmoyer", # PR #25576 (credential_pool ISO rehydrate) + "258858106+shellybotmoyer@users.noreply.github.com": "shellybotmoyer", + "33156212+ether-btc@users.noreply.github.com": "ether-btc", # PR #26632 (memory provider whitespace guard) + "Bloomtonjovish@gmail.com": "LifeJiggy", # PR #26516 (paste collapse logging) + "141562589+LifeJiggy@users.noreply.github.com": "LifeJiggy", + "beastant1@gmail.com": "nekwo", # PR #26481 (PS5.1 UTF-8 BOM) + "43717185+nekwo@users.noreply.github.com": "nekwo", + "67979730+flooryyyy@users.noreply.github.com": "flooryyyy", # PR #26374 (tool_trace error detection) + "188585318+dgians@users.noreply.github.com": "dgians", # PR #26034 (.ts/.py/.sh docs types) + "zealy@tz.co": "dgians", # PR #26034 (bot-committed by zealy-tzco under dgians' PR) + "mottei.survive@gmail.com": "flanny7", # PR #27030 (setup_open_webui python var) + "20530505+flanny7@users.noreply.github.com": "flanny7", + "hermesagent26@gmail.com": "hermesagent26", # PR #26438 (kimi model-name reasoning pad) + "276067471+hermesagent26@users.noreply.github.com": "hermesagent26", + "71590782+kriscolab@users.noreply.github.com": "kriscolab", # PR #26926 (deepseek default_aux_model) }