fix: improve Slack gateway socket health recovery#1
Merged
Conversation
Pass the user's configured api_key through local-server detection and context-length probes (detect_local_server_type, _query_local_context_length, query_ollama_num_ctx) and use LM Studio's native /api/v1/models endpoint in fetch_endpoint_model_metadata when a loaded instance is present — so the probed context length is the actual runtime value the user loaded the model at, not just the model's theoretical max. Helps local-LLM users whose auto-detected context length was wrong, causing compression failures and context-overrun crashes.
Follow-up for salvaged PR NousResearch#3185: - run_agent.py: pass self.api_key to query_ollama_num_ctx() so Ollama behind an auth proxy (same issue class as the LM Studio fix) can be probed successfully. - scripts/release.py AUTHOR_MAP: map @tannerfokkens-maker's local-hostname commit email.
Credit preserved for PR NousResearch#13270 (WhatsApp Windows disconnect fix).
Users can declare shell scripts in config.yaml under a hooks: block that fire on plugin-hook events (pre_tool_call, post_tool_call, pre_llm_call, subagent_stop, etc). Scripts receive JSON on stdin, can return JSON on stdout to block tool calls or inject context pre-LLM. Key design: - Registers closures on existing PluginManager._hooks dict — zero changes to invoke_hook() call sites - subprocess.run(shell=False) via shlex.split — no shell injection - First-use consent per (event, command) pair, persisted to allowlist JSON - Bypass via --accept-hooks, HERMES_ACCEPT_HOOKS=1, or hooks_auto_accept - hermes hooks list/test/revoke/doctor CLI subcommands - Adds subagent_stop hook event fired after delegate_task children exit - Claude Code compatible response shapes accepted Cherry-picked from PR NousResearch#13143 by @pefontana.
Requests through Vercel AI Gateway now carry referrerUrl / appName / User-Agent attribution so traffic shows up in the gateway's analytics. Adds _AI_GATEWAY_HEADERS in auxiliary_client and a new ai-gateway.vercel.sh branch in _apply_client_headers_for_base_url.
- Curated AI_GATEWAY_MODELS list in hermes_cli/models.py (OSS first, kimi-k2.5 as recommended default). - fetch_ai_gateway_models() filters the curated list against the live /v1/models catalog; falls back to the snapshot on network failure. - fetch_ai_gateway_pricing() translates Vercel's input/output field names to the prompt/completion shape the shared picker expects; carries input_cache_read / input_cache_write through unchanged. - get_pricing_for_provider() now handles ai-gateway. - _model_flow_ai_gateway() provides a guided URL prompt when no key is set and a pricing-column picker; routes ai-gateway to it instead of the generic api-key flow.
Moves Vercel AI Gateway from the bottom of the list to near the top, adjacent to other multi-model aggregators. The existing bottom position was a result of the list growing by appending new providers over time — the new position makes it more discoverable.
When the live Vercel AI Gateway catalog exposes a Moonshot model with zero input AND output pricing, it's promoted to position #1 as the recommended default — even if the exact ID isn't in the curated AI_GATEWAY_MODELS list. This enables dynamic discovery of new free Moonshot variants without requiring a PR to update curation. Paid Moonshot models are unaffected; falls back to the normal curated recommended tag when no free Moonshot is live.
Vercel provides a d?to= redirect URL that routes users through their team picker to the AI Gateway API keys management page. Using this specific URL lands users directly on the "Create key" page instead of the generic AI Gateway dashboard.
Adds zheng.jerilyn@gmail.com → jerilynzheng to scripts/release.py so the check-attribution CI workflow passes.
The original list was copied from OpenRouter conventions and didn't match what Vercel actually hosts. Verified against the live /v1/models endpoint (266 models): - qwen/qwen3.6-plus → alibaba/qwen3.6-plus (Vercel hosts Qwen under alibaba/) - z-ai/glm-5.1 → zai/glm-5.1 (no hyphen) - x-ai/grok-4.20 → xai/grok-4.20-reasoning (no hyphen, picks reasoning variant) - google/gemini-3-flash-preview → google/gemini-3-flash (no -preview suffix) - moonshotai/kimi-k2.5 → moonshotai/kimi-k2.6 (newest available)
NousResearch#13300) Load-time sanitizer silently removed non-ASCII codepoints from any env var ending in _API_KEY / _TOKEN / _SECRET / _KEY, turning copy-paste artifacts (Unicode lookalikes, ZWSP, NBSP) into opaque provider-side API_KEY_INVALID errors. Warn once per key to stderr with the offending codepoints (U+XXXX) and guidance to re-copy from the provider dashboard.
…ites Aslaaen's fix in the original PR covered _detect_api_mode_for_url and the two openai/xai sites in run_agent.py. This finishes the sweep: the same substring-match false-positive class (e.g. https://api.openai.com.evil/v1, https://proxy/api.openai.com/v1, https://api.anthropic.com.example/v1) existed in eight more call sites, and the hostname helper was duplicated in two modules. - utils: add shared base_url_hostname() (single source of truth). - hermes_cli/runtime_provider, run_agent: drop local duplicates, import from utils. Reuse the cached AIAgent._base_url_hostname attribute everywhere it's already populated. - agent/auxiliary_client: switch codex-wrap auto-detect, max_completion_tokens gate (auxiliary_max_tokens_param), and custom-endpoint max_tokens kwarg selection to hostname equality. - run_agent: native-anthropic check in the Claude-style model branch and in the AIAgent init provider-auto-detect branch. - agent/model_metadata: Anthropic /v1/models context-length lookup. - hermes_cli/providers.determine_api_mode: anthropic / openai URL heuristics for custom/unknown providers (the /anthropic path-suffix convention for third-party gateways is preserved). - tools/delegate_tool: anthropic detection for delegated subagent runtimes. - hermes_cli/setup, hermes_cli/tools_config: setup-wizard vision-endpoint native-OpenAI detection (paired with deduping the repeated check into a single is_native_openai boolean per branch). Tests: - tests/test_base_url_hostname.py covers the helper directly (path-containing-host, host-suffix, trailing dot, port, case). - tests/hermes_cli/test_determine_api_mode_hostname.py adds the same regression class for determine_api_mode, plus a test that the /anthropic third-party gateway convention still wins. Also: add asslaenn5@gmail.com → Aslaaen to scripts/release.py AUTHOR_MAP.
Completes the hostname-hardening sweep — every substring check against a provider host in live-routing code is now hostname-based. This closes the same false-positive class for OpenRouter, GitHub Copilot, Kimi, Qwen, ChatGPT/Codex, Bedrock, GitHub Models, Vercel AI Gateway, Nous, Z.AI, Moonshot, Arcee, and MiniMax that the original PR closed for OpenAI, xAI, and Anthropic. New helper: - utils.base_url_host_matches(base_url, domain) — safe counterpart to 'domain in base_url'. Accepts hostname equality and subdomain matches; rejects path segments, host suffixes, and prefix collisions. Call sites converted (real-code only; tests, optional-skills, red-teaming scripts untouched): run_agent.py (10 sites): - AIAgent.__init__ Bedrock branch, ChatGPT/Codex branch (also path check) - header cascade for openrouter / copilot / kimi / qwen / chatgpt - interleaved-thinking trigger (openrouter + claude) - _is_openrouter_url(), _is_qwen_portal() - is_native_anthropic check - github-models-vs-copilot detection (3 sites) - reasoning-capable route gate (nousresearch, vercel, github) - codex-backend detection in API kwargs build - fallback api_mode Bedrock detection agent/auxiliary_client.py (7 sites): - extra-headers cascades in 4 distinct client-construction paths (resolve custom, resolve auto, OpenRouter-fallback-to-custom, _async_client_from_sync, resolve_provider_client explicit-custom, resolve_auto_with_codex) - _is_openrouter_client() base_url sniff agent/usage_pricing.py: - resolve_billing_route openrouter branch agent/model_metadata.py: - _is_openrouter_base_url(), Bedrock context-length lookup hermes_cli/providers.py: - determine_api_mode Bedrock heuristic hermes_cli/runtime_provider.py: - _is_openrouter_url flag for API-key preference (issues NousResearch#420, NousResearch#560) hermes_cli/doctor.py: - Kimi User-Agent header for /models probes tools/delegate_tool.py: - subagent Codex endpoint detection trajectory_compressor.py: - _detect_provider() cascade (8 providers: openrouter, nous, codex, zai, kimi-coding, arcee, minimax-cn, minimax) cli.py, gateway/run.py: - /model-switch cache-enabled hint (openrouter + claude) Bedrock detection tightened from 'bedrock-runtime in url' to 'hostname starts with bedrock-runtime. AND host is under amazonaws.com'. ChatGPT/Codex detection tightened from 'chatgpt.com/backend-api/codex in url' to 'hostname is chatgpt.com AND path contains /backend-api/codex'. Tests: - tests/test_base_url_hostname.py extended with a base_url_host_matches suite (exact match, subdomain, path-segment rejection, host-suffix rejection, host-prefix rejection, empty-input, case-insensitivity, trailing dot). Validation: 651 targeted tests pass (runtime_provider, minimax, bedrock, gemini, auxiliary, codex_cloudflare, usage_pricing, compressor_fallback, fallback_model, openai_client_lifecycle, provider_parity, cli_provider_resolution, delegate, credential_pool, context_compressor, plus the 4 hostname test modules). 26-assertion E2E call-site verification across 6 modules passes.
…NousResearch#13340) The mid-run steer marker was '[USER STEER (injected mid-run, not tool output): <text>]'. Replaced with a plain two-newline-prefixed 'User guidance: <text>' suffix. Rationale: the marker lives inside the tool result's content string regardless of whether the tool returned JSON, plain text, an MCP result, or a plugin result. The bracketed tag read like structured metadata that some tools (terminal, execute_code) could confuse with their own output formatting. A plain labelled suffix works uniformly across every content shape we produce. Behavior unchanged: - Still injected into the last tool-role message's content. - Still preserves multimodal (Anthropic) content-block lists by appending a text block. - Still drained at both sites added in NousResearch#12959 and NousResearch#13205 — per-tool drain between individual calls, and pre-API-call drain at the top of each main-loop iteration. Checked Codex's equivalent (pending_input / inject_user_message_without_turn in codex-rs/core): they record mid-turn user input as a real role:user message via record_user_prompt_and_emit_turn_item(). That's cleaner for their Responses-API model but not portable to Chat Completions where role alternation after tool_calls is strict. Embedding the guidance in the last tool result remains the correct placement for us. Validation: all 21 tests in tests/run_agent/test_steer.py pass.
…esearch#13304) Delete the stale literal `_PROVIDER_MODELS["ai-gateway"]` (gpt-5, gemini-2.5-pro, claude-4.5 — outdated the moment PR NousResearch#13223 landed with its curated `AI_GATEWAY_MODELS` snapshot) and derive it from `AI_GATEWAY_MODELS` instead, so the picker tuples and the bare-id fallback catalog stay in sync automatically. Also fixes `get_default_model_for_provider('ai-gateway')` to return kimi-k2.6 (the curated recommendation) instead of claude-opus-4.6.
…h#13339) The WhatsApp bridge depends on @whiskeysockets/baileys pulled directly from a GitHub commit tarball, which on slower connections or when GitHub is sluggish routinely exceeds 120s. The hardcoded timeout surfaced as a raw TimeoutExpired traceback during 'hermes whatsapp' setup. Switch to the same pattern used by the TUI npm install at line ~945: no timeout, --no-fund/--no-audit/--progress=false to keep output clean, stderr captured and tailed on failure. Also resolve npm via shutil.which so missing Node.js gives a clean error instead of FileNotFoundError, and handle Ctrl+C cleanly. Co-authored-by: teknium1 <teknium@nousresearch.com>
…#13354) Classic-CLI /steer typed during an active agent run was queued through self._pending_input alongside ordinary user input. process_loop, which drains that queue, is blocked inside self.chat() for the entire run, so the queued command was not pulled until AFTER _agent_running had flipped back to False — at which point process_command() took the idle fallback ("No agent running; queued as next turn") and delivered the steer as an ordinary next-turn user message. From Utku's bug report on PR NousResearch#13205: mid-run /steer arrived minutes later at the end of the turn as a /queue-style message, completely defeating its purpose. Fix: add _should_handle_steer_command_inline() gating — when _agent_running is True and the user typed /steer, dispatch process_command(text) directly from the prompt_toolkit Enter handler on the UI thread instead of queueing. This mirrors the existing _should_handle_model_command_inline() pattern for /model and is safe because agent.steer() is thread-safe (uses _pending_steer_lock, no prompt_toolkit state mutation, instant return). No changes to the idle-path behavior: /steer typed with no active agent still takes the normal queue-and-drain route so the fallback "No agent running; queued as next turn" message is preserved. Validation: - 7 new unit tests in tests/cli/test_cli_steer_busy_path.py covering the detector, dispatch path, and idle-path control behavior. - All 21 existing tests in tests/run_agent/test_steer.py still pass. - Live PTY end-to-end test with real agent + real openrouter model: 22:36:22 API call #1 (model requested execute_code) 22:36:26 ENTER FIRED: agent_running=True, text='/steer ...' 22:36:26 INLINE STEER DISPATCH fired 22:36:43 agent.log: 'Delivered /steer to agent after tool batch' 22:36:44 API call NousResearch#2 included the steer; response contained marker Same test on the tip of main without this fix shows the steer landing as a new user turn ~20s after the run ended.
Add agent/transports/types.py with three shared dataclasses: - NormalizedResponse: content, tool_calls, finish_reason, reasoning, usage, provider_data - ToolCall: id, name, arguments, provider_data (per-tool-call protocol metadata) - Usage: prompt_tokens, completion_tokens, total_tokens, cached_tokens Add normalize_anthropic_response_v2() to anthropic_adapter.py — wraps the existing v1 function and maps its output to NormalizedResponse. One call site in run_agent.py (the main normalize branch) uses v2 with a back-compat shim to SimpleNamespace for downstream code. No ABC, no registry, no streaming, no client lifecycle. Those land in PR 3 with the first concrete transport (AnthropicTransport). 46 new tests: - test_types.py: dataclass construction, build_tool_call, map_finish_reason - test_anthropic_normalize_v2.py: v1-vs-v2 regression tests (text, tools, thinking, mixed, stop reasons, mcp prefix stripping, edge cases) Part of the provider transport refactor (PR 2 of 9).
…nts (NousResearch#13363) Catalog snapshots, config version literals, and enumeration counts are data that changes as designed. Tests that assert on those values add no behavioral coverage — they just break CI on every routine update and cost engineering time to 'fix.' Replace with invariants where one exists, delete where none does. Deleted (pure snapshots): - TestMinimaxModelCatalog (3 tests): 'MiniMax-M2.7 in models' et al - TestGeminiModelCatalog: 'gemini-2.5-pro in models', 'gemini-3.x in models' - test_browser_camofox_state::test_config_version_matches_current_schema (docstring literally said it would break on unrelated bumps) Relaxed (keep plumbing check, drop snapshot): - Xiaomi / Arcee / Kimi moonshot / Kimi coding / HuggingFace static lists: now assert 'provider exists and has >= 1 entry' instead of specific names - HuggingFace main/models.py consistency test: drop 'len >= 6' floor Dynamicized (follow source, not a literal): - 3x test_config.py migration tests: raw['_config_version'] == DEFAULT_CONFIG['_config_version'] instead of hardcoded 21 Fixed stale tests against intentional behavior changes: - test_insights::test_gateway_format_hides_cost: name matches new behavior (no dollar figures); remove contradicting '$' in text assertion - test_config::prefers_api_then_url_then_base_url: flipped per PR NousResearch#9332; rename + update to base_url > url > api - test_anthropic_adapter: relax assert_called_once() (xdist-flaky) to assert called — contract is 'credential flowed through' - test_interrupt_propagation: add provider/model/_base_url to bare-agent fixture so the stale-timeout code path resolves Fixed stale integration tests against opt-in plugin gate: - transform_tool_result + transform_terminal_output: write plugins.enabled allow-list to config.yaml and reset the plugin manager singleton Source fix (real consistency invariant): - agent/model_metadata.py: add moonshotai/Kimi-K2.6 context length (262144, same as K2.5). test_model_metadata_has_context_lengths was correctly catching the gap. Policy: - AGENTS.md Testing section: new subsection 'Don't write change-detector tests' with do/don't examples. Reviewers should reject catalog-snapshot assertions in new tests. Covers every test that failed on the last completed main CI run (24703345583) except test_modal_sandbox_fixes::test_terminal_tool_present + test_terminal_and_file_toolsets_resolve_all_tools, which now pass both alone and with the full tests/tools/ directory (xdist ordering flake that resolved itself).
…search#13364) The re-pair branch had a redundant 'import shutil' inside cmd_whatsapp, which made shutil a function-local throughout the whole scope. The earlier 'shutil.which("npm")' call at the dependency-install step then crashed with UnboundLocalError before control ever reached the local import. shutil is already imported at module level (line 48), so the local import was dead code anyway. Drop it.
… box (NousResearch#13384) * feat(skills): inject absolute skill dir and expand ${HERMES_SKILL_DIR} templates When a skill loads, the activation message now exposes the absolute skill directory and substitutes ${HERMES_SKILL_DIR} / ${HERMES_SESSION_ID} tokens in the SKILL.md body, so skills with bundled scripts can instruct the agent to run them by absolute path without an extra skill_view round-trip. Also adds opt-in inline-shell expansion: !`cmd` snippets in SKILL.md are pre-executed (with the skill directory as CWD) and their stdout is inlined into the message before the agent reads it. Off by default — enable via skills.inline_shell in config.yaml — because any snippet runs on the host without approval. Changes: - agent/skill_commands.py: template substitution, inline-shell expansion, absolute skill-dir header, supporting-files list now shows both relative and absolute forms. - hermes_cli/config.py: new skills.template_vars, skills.inline_shell, skills.inline_shell_timeout knobs. - tests/agent/test_skill_commands.py: coverage for header, both template tokens (present and missing session id), template_vars disable, inline-shell default-off, enabled, CWD, and timeout. - website/docs/developer-guide/creating-skills.md: documents the template tokens, the absolute-path header, and the opt-in inline shell with its security caveat. Validation: tests/agent/ 1591 passed (includes 9 new tests). E2E: loaded a real skill in an isolated HERMES_HOME; confirmed ${HERMES_SKILL_DIR} resolves to the absolute path, ${HERMES_SESSION_ID} resolves to the passed task_id, !`date` runs when opt-in is set, and stays literal when it isn't. * feat(terminal): source ~/.bashrc (and user-listed init files) into session snapshot bash login shells don't source ~/.bashrc, so tools that install themselves there — nvm, asdf, pyenv, cargo, custom PATH exports — stay invisible to the environment snapshot Hermes builds once per session. Under systemd or any context with a minimal parent env, that surfaces as 'node: command not found' in the terminal tool even though the binary is reachable from every interactive shell on the machine. Changes: - tools/environments/local.py: before the login-shell snapshot bootstrap runs, prepend guarded 'source <file>' lines for each resolved init file. Missing files are skipped, each source is wrapped with a '[ -r ... ] && . ... || true' guard so a broken rc can't abort the bootstrap. - hermes_cli/config.py: new terminal.shell_init_files (explicit list, supports ~ and ${VAR}) and terminal.auto_source_bashrc (default on) knobs. When shell_init_files is set it takes precedence; when it's empty and auto_source_bashrc is on, ~/.bashrc gets auto-sourced. - tests/tools/test_local_shell_init.py: 10 tests covering the resolver (auto-bashrc, missing file, explicit override, ~/${VAR} expansion, opt-out) and the prelude builder (quoting, guarded sourcing), plus a real-LocalEnvironment snapshot test that confirms exports in the init file land in subsequent commands' environment. - website/docs/reference/faq.md: documents the fix in Troubleshooting, including the zsh-user pattern of sourcing ~/.zshrc or nvm.sh directly via shell_init_files. Validation: 10/10 new tests pass; tests/tools/test_local_*.py 40/40 pass; tests/agent/ 1591/1591 pass; tests/hermes_cli/test_config.py 50/50 pass. E2E in an isolated HERMES_HOME: confirmed that a fake ~/.bashrc setting a marker var and PATH addition shows up in a real LocalEnvironment().execute() call, that auto_source_bashrc=false suppresses it, that an explicit shell_init_files entry wins over the auto default, and that a missing bashrc is silently skipped.
…-preserve-segments fix(tui): preserve prior segment output on Ctrl+C interrupt
…ne-parity-linux fix(tui): readline parity on Linux — Ctrl+A = home, Alt+B/F word nav
…arch#13703) Revert two overreaches from NousResearch#13699 that forced paid Nous vision to xiaomi/mimo-v2-omni instead of the tier-appropriate gemini-3-flash-preview: 1. Remove "nous": "xiaomi/mimo-v2-omni" from _PROVIDER_VISION_MODELS — NousResearch#13696 already routes nous main-provider vision through the strict backend, and this entry caused any direct resolve_provider_client( "nous", ...) aggregator-lookup path to pick the wrong model for paid. 2. Drop the 'elif vision' paid override in _try_nous() that forced mimo-v2-omni on every Nous vision call regardless of tier. Paid accounts now keep gemini-3-flash-preview for vision as well as text. Free-tier behavior unchanged: still uses mimo-v2-omni for vision, mimo-v2-pro for text (check_nous_free_tier() branch). E2E verified: paid vision → google/gemini-3-flash-preview free vision → xiaomi/mimo-v2-omni paid text → google/gemini-3-flash-preview free text → xiaomi/mimo-v2-pro
…signals from llm-wiki-compiler (NousResearch#13700) Three additive conventions inspired by github.com/atomicmemory/llm-wiki-compiler: - Paragraph-level provenance: `^[raw/articles/source.md]` markers on pages synthesizing 3+ sources, so readers can trace individual claims without re-reading full source files. - Raw source content hashing: `sha256:` in raw/ frontmatter enables re-ingest drift detection — skip unchanged sources, flag changed ones. - Optional `confidence` and `contested` frontmatter fields let lint surface weak or disputed claims without re-reading every page's prose. Lint gains two new checks (quality signals, source drift) and one expanded check (contradictions now surfaces frontmatter-flagged pages). Also adds a Related Tools section pointing users who want batch/scheduled compilation at llm-wiki-compiler (Obsidian-compatible, works on the same vault). All additions are opt-in — existing wikis need no migration. Skill version 2.0.0 -> 2.1.0.
The inline markdown regex had `~([^~\s][^~]*?)~` for Pandoc-style subscript
(H~2~O, CO~2~). On models that decorate prose with kaomoji like `thing ~!`
and `cool ~?` — Kimi especially — the opener `~!` paired with the next
stray `~` on the line and dim-formatted everything between them with a
leading `_` character, mangling markdown output.
Tighten the pattern to short alphanumeric-only content (`~[A-Za-z0-9]{1,8}~`)
since real subscript never contains punctuation, spaces, or long runs.
Same tightening applied to stripInlineMarkup so width measurement stays
consistent. Classic CLI was unaffected because it renders these literally.
- Drop the outer no-op capture group from INLINE_RE and restructure the source as an ordered list of patterns-with-index-comments so each alternative is individually greppable. Shift group indices in MdInline down by one accordingly. - Inline single-use helpers (parseFence, isFenceClose, isMarkdownFence, trimBareUrl) and intermediate variables (path, lang, raw, prefix, body, depth, task body, setext match, etc.). - Hoist block-level regexes used inside MdImpl (FENCE_CLOSE_RE, SETEXT_RE, BULLET_RE, TASK_RE, NUMBERED_RE, QUOTE_RE) to top-level consts so they're compiled once instead of per-line. - Collapse the duplicate compact-vs-normal blank-line branches into one if/!compact gap call. - Move Fence and MdProps types to the bottom per house style. - Shorten splitTableRow → splitRow and use optional chaining in a few match sites. No behavior change; 162/162 tests pass. Net -22 LoC.
…wn-tilde-subscript fix(tui): don't swallow Kimi/Qwen ~! ~? kaomoji as subscript spans
…wn-cleanup refactor(tui): clean markdown.tsx per KISS/DRY
Reported during TUI v2 blitz retest: /resume modal only surfaced tui/cli rows, even though `hermes --tui --resume <id>` with a pasted telegram session id works fine. The handler double-fetched with explicit `source="tui"` and `source="cli"` filters and dropped everything else on the floor. Drop the filter — list_sessions_rich(source=None) already excludes child sessions (subagents, compression continuations) via its default, and users want to resume messenger sessions from inside the TUI. Adds gateway regression coverage.
Reported during TUI v2 blitz retest: typing a multi-line message with shift-Enter and then pressing Up to edit an earlier line swapped the whole buffer for the previous history entry instead of moving the cursor up a line. Down then restored the draft → the buffer appeared to "flip" between the draft and a prior prompt. `useInputHandlers` cycles history on Up/Down, but textInput only checked `inputBuf.length` — that only counts lines committed with a trailing backslash, not shift-Enter newlines inside `input` itself. Fix: detect logical lines inside the input string and move the cursor one line up/down preserving column offset (clamp to line end when the destination is shorter, standard editor behavior). Only fall through to history cycling when the cursor is already on the first line (Up) or last line (Down). Adds unit coverage for the new `lineNav` helper.
Reported during TUI v2 blitz retest: `/history` in the TUI only shows prompts from non-TUI Hermes runs and can't scroll the window. Root cause is the slash-worker subprocess: it's a detached HermesCLI that never sees the TUI's turns, so its `conversation_history` starts empty and `show_history` surfaces whatever was persisted from earlier CLI sessions — not what the user just did inside the TUI. Intercept `/history` as a local slash command so it dumps `ctx.local.getHistoryItems()` — the TUI's own transcript — routed through the pager (which scrolls after NousResearch#13591). Accepts an optional preview-length argument (default 400 chars per message). Adds createSlashHandler coverage.
…bagents (NousResearch#13718) * feat(models): hide OpenRouter models that don't advertise tool support Port from Kilo-Org/kilocode#9068. hermes-agent is tool-calling-first — every provider path assumes the model can invoke tools. Models whose OpenRouter supported_parameters doesn't include 'tools' (e.g. image-only or completion-only models) cannot be driven by the agent loop and fail at the first tool call. Filter them out of fetch_openrouter_models() so they never appear in the model picker (`hermes model`, setup wizard, /model slash command). Permissive when the field is missing — OpenRouter-compatible gateways (Nous Portal, private mirrors, older snapshots) don't always populate supported_parameters. Treat missing as 'unknown → allow' rather than silently emptying the picker on those gateways. Only hide models whose supported_parameters is an explicit list that omits tools. Tests cover: tools present → kept, tools absent → dropped, field missing → kept, malformed non-list → kept, non-dict item → kept, empty list → dropped. * feat(delegate): cross-agent file state coordination for concurrent subagents Prevents mangled edits when concurrent subagents touch the same file (same process, same filesystem — the mangle scenario from NousResearch#11215). Three layers, all opt-out via HERMES_DISABLE_FILE_STATE_GUARD=1: 1. FileStateRegistry (tools/file_state.py) — process-wide singleton tracking per-agent read stamps and the last writer globally. check_stale() names the sibling subagent in the warning when a non-owning agent wrote after this agent's last read. 2. Per-path threading.Lock wrapped around the read-modify-write region in write_file_tool and patch_tool. Concurrent siblings on the same path serialize; different paths stay fully parallel. V4A multi-file patches lock in sorted path order (deadlock-free). 3. Delegate-completion reminder in tools/delegate_tool.py: after a subagent returns, writes_since(parent, child_start, parent_reads) appends '[NOTE: subagent modified files the parent previously read — re-read before editing: ...]' to entry.summary when the child touched anything the parent had already seen. Complements (does not replace) the existing path-overlap check in run_agent._should_parallelize_tool_batch — batch check prevents same-file parallel dispatch within one agent's turn (cheap prevention, zero API cost), registry catches cross-subagent and cross-turn staleness at write time (detection). Behavior is warning-only, not hard-failing — matches existing project style. Errors surface naturally: sibling writes often invalidate the old_string in patch operations, which already errors cleanly. Tests: tests/tools/test_file_state_registry.py — 16 tests covering registry state transitions, per-path locking, per-path-not-global locking, writes_since filtering, kill switch, and end-to-end integration through the real read_file/write_file/patch handlers.
Follow-up on NousResearch#13726 from blitz feedback: Up/Down history cycling should only trigger when the caret is at the start/end boundary (or the input is empty).\n\nPreviously useInputHandlers intercepted arrows whenever inputBuf was empty, which still stole Up/Down from normal multiline editing. textInput now publishes caret position through inputSelectionStore even with no active selection, and useInputHandlers gates history/queue cycling on those boundaries.
Follow-up on NousResearch#13724: showing literally every source was too noisy.\n\n now fetches a wider window (, larger limit) and then filters to a curated allowlist of human-facing sources (tui/cli plus chat adapters like telegram/discord/slack/whatsapp/etc). This keeps row NousResearch#7 fixed (telegram sessions visible in /resume) without surfacing internal source kinds such as tool/acp.
Follow-up on multiline arrow behavior: Up/Down now fall back to queue/history whenever there is no logical line above/below the caret (not only at absolute start/end character positions). This makes Up from the end of the top line cycle history, matching expected readline-ish behavior.
…ine-up-arrow fix(tui): up-arrow inside a multi-line buffer moves cursor, not history
…-all-sources fix(tui): /resume picker shows telegram/discord/etc sessions
…y-local fix(tui): /history shows the TUI's own transcript, scrollable
NousResearch#13743) A single global MAX_TEXT_LENGTH = 4000 truncated every TTS provider at 4000 chars, causing long inputs to be silently chopped even though the underlying APIs allow much more: - OpenAI: 4096 - xAI: 15000 - MiniMax: 10000 - ElevenLabs: 5000 / 10000 / 30000 / 40000 (model-aware) - Gemini: ~5000 - Edge: ~5000 The schema description also told the model 'Keep under 4000 characters', which encouraged the agent to self-chunk long briefs into multiple TTS calls (producing 3 separate audio files instead of one). New behavior: - PROVIDER_MAX_TEXT_LENGTH table + ELEVENLABS_MODEL_MAX_TEXT_LENGTH encode the documented per-provider limits. - _resolve_max_text_length(provider, cfg) resolves: 1. tts.<provider>.max_text_length user override 2. ElevenLabs model_id lookup 3. provider default 4. 4000 fallback - text_to_speech_tool() and stream_tts_to_speaker() both call the resolver; old MAX_TEXT_LENGTH alias kept for back-compat. - Schema description no longer hardcodes 4000. Tests: 27 new unit + E2E tests; all 53 existing TTS tests and 253 voice-command/voice-cli tests still pass.
Port the upstream baoyu-comic skill to Hermes' tool ecosystem, matching the earlier baoyu-infographic adaptation: - metadata namespace openclaw -> hermes (+ tags, homepage) - drop EXTEND.md preferences system (references/config/ removed, workflow Step 1.1 removed) - user prompts via clarify (one question at a time) instead of AskUserQuestion batches - image generation via image_generate instead of baoyu-imagine, with aspect-ratio mapping to landscape/portrait/square - Windows/PowerShell/WSL shell snippets dropped - file I/O referenced via Hermes write_file/read_file tools - CLI-style --flags converted to natural-language options and user-intent cues (skill matching has no slash command trigger) Add PORT_NOTES.md documenting the adaptations and a sync procedure. Art-style/tone/layout reference files are preserved verbatim from upstream v1.56.1.
- Remove PDF merge feature and scripts/ directory (no pdf-lib dep)
- Correct image_generate docs: prompt-only, returns URL; add
curl download step after every call
- Downgrade reference images to text-based trait extraction
(style/palette/scene); character sheet is agent-facing reference
- Unify source file naming on source-{slug}.md across SKILL.md
and workflow.md
Page prompts are written in Step 5 from the text descriptions in characters/characters.md — the PNG sheet generated in Step 7.1 cannot be used to write them. Reposition the PNG as a human-facing review artifact (and reference for later regenerations / manual edits), and drop the confusing "Character sheet | Strategy" tables since the embedding rule is uniform.
Fills the three gaps left by the orchestrator/width-depth salvage: - configuration.md §Delegation: max_concurrent_children, max_spawn_depth, orchestrator_enabled are now in the canonical config.yaml reference with a paragraph covering defaults, clamping, role-degradation, and the 3x3x3=27-leaf cost scaling. - environment-variables.md: adds DELEGATION_MAX_CONCURRENT_CHILDREN to the Agent Behavior table. - features/delegation.md: corrects stale 'default 5, cap 8' wording (that was from the original PR; the salvage landed on default 3 with no ceiling and a tool error on excess instead of truncation).
…rt (NousResearch#13747) website/src/pages/skills/index.tsx imports ../../data/skills.json, but that file is git-ignored and generated at build time by website/scripts/extract-skills.py. CI workflows (deploy-site.yml, docs-site-checks.yml) run the script explicitly before 'npm run build', so production and PR checks always work — but 'npm run build' on a contributor's machine fails with: Module not found: Can't resolve '../../data/skills.json' because the extraction step was never wired into the npm scripts. Adds a prebuild/prestart hook that runs extract-skills.py automatically. If python3 or pyyaml aren't installed locally, writes an empty skills.json instead of hard-failing — the Skills Hub page renders with an empty state, the rest of the site builds normally, and CI (which always has the deps) still generates the full catalog for production.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary\n- add Slack-side socket watchdog and socket task supervision\n- force reconnect when Socket Mode task exits or goes idle too long\n- track Slack activity and write reconnect/degraded health status\n- tune the default watchdog idle threshold from 60s to 120s\n- add regression tests for watchdog behavior\n\n## Test Plan\n- python -m pytest tests/gateway/test_slack.py::TestSlackSocketWatchdog -q -o 'addopts='\n- python -m pytest tests/gateway/test_slack.py -q -o 'addopts='\n- python -m pytest tests/gateway/test_fast_command.py -q -o 'addopts='\n