Skip to content

feat(cli): up/down arrows move cursor within multiline text#4

Open
StefanIsMe wants to merge 518 commits intomainfrom
feat/cli-history-nav
Open

feat(cli): up/down arrows move cursor within multiline text#4
StefanIsMe wants to merge 518 commits intomainfrom
feat/cli-history-nav

Conversation

@StefanIsMe
Copy link
Copy Markdown
Owner

Summary

Fixes cursor movement in the TUI prompt box so up/down arrows always move the cursor vertically within multiline text, instead of sometimes triggering history browsing.

Problem

The TUI prompt box is multiline. The previous implementation used buffer.auto_up/auto_down for up/down arrow keys. These methods have a dual behavior:

  • Cursor movement within text when cursor is NOT at first/last character
  • History browsing when cursor IS at the first/last character

When typing a multi-line prompt, the cursor is typically mid-text (not at boundaries), but pressing up/down would sometimes still trigger history browsing instead of moving the cursor to the line above/below — unexpected and inconsistent.

Solution

  • Up arrow → always move cursor up one row within text
  • Down arrow → always move cursor down one row within text
  • Ctrl+Up → browse to previous command (history escape hatch)
  • Ctrl+Down → browse to next command (history escape hatch)

Uses explicit get_cursor_position_up()/get_cursor_position_down() + set_cursor_position() instead of auto_up/auto_down to eliminate the history-browsing fallback.

Files changed

cli.py: 1 file, +40/-9 lines

Testing

  • Syntax check passes
  • Up/down moves cursor within multiline text (main case)
  • Ctrl+up/down browses command history (escape hatch)
  • Single-line mode: behavior unchanged

teknium1 and others added 30 commits March 30, 2026 13:37
* fix(gateway): honor default for invalid bool-like config values

* refactor: simplify web backend priority detection

Replace cascading boolean conditions with a priority-ordered loop.
Same behavior (verified against all 16 env var combinations),
half the lines, trivially extensible for new backends.

---------

Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
* fix(gateway): honor default for invalid bool-like config values

* refactor: simplify web backend priority detection

Replace cascading boolean conditions with a priority-ordered loop.
Same behavior (verified against all 16 env var combinations),
half the lines, trivially extensible for new backends.

* fix(tools): show browser and TTS in reconfigure menu

_toolset_has_keys() returned False for toolsets with no-key providers
(Local Browser, Edge TTS) because it only checked providers with
env_vars. Users couldn't find these tools in the reconfigure list
and had no obvious way to switch browser/TTS backends.

Now treats providers with empty env_vars as always-configured, so
toolsets with free/local options always appear in the reconfigure menu.

---------

Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
…le pattern

- composition.md: add text backdrop (gaussian dark mask behind glyphs) and
  external layout oracle pattern (browser-based text layout → JSON → Python
  renderer pipeline for obstacle-aware text reflow)
- shaders.md: add reverse vignette shader (center-darkening for text readability)
- troubleshooting.md: add diagnostic entries for text-over-busy-background
  readability and kaleidoscope-destroys-text pitfall
…xt-readability-and-layout-oracle

ascii-video skill: text readability techniques and external layout oracle
NousResearch#4037)

Multiple agents/profiles running 'hermes honcho setup' all wrote to
the shared global ~/.honcho/config.json, overwriting each other's
configuration.

Root cause: _write_config() defaulted to resolve_config_path() which
returns the global path when no instance-local file exists yet (i.e.
on first setup).

Fix: _write_config() now defaults to _local_config_path() which always
returns $HERMES_HOME/honcho.json. Each profile gets its own config file.
Reading still falls back to global for cross-app interop and seeding.

Also updates cmd_setup and cmd_status messaging to show the actual
write path.

Includes 10 new tests verifying profile isolation, global fallback
reads, and multi-profile independence.
…rch#4081)

* fix: rate-limit pairing rejection messages to prevent spam

When generate_code() returns None (rate limited or max pending), the
"Too many pairing requests" message was sent on every subsequent DM
with no cooldown. A user sending 30 messages would get 30 rejection
replies — reported as potential hack on WhatsApp.

Now check _is_rate_limited() before any pairing response, and record
rate limit after sending a rejection. Subsequent messages from the
same user are silently ignored until the rate limit window expires.

* test: add coverage for pairing response rate limiting

Follow-up to cherry-picked PR NousResearch#4042 — adds tests verifying:
- Rate-limited users get silently ignored (no response sent)
- Rejection messages record rate limit for subsequent suppression

---------

Co-authored-by: 0xbyt4 <35742124+0xbyt4@users.noreply.github.com>
…sion (NousResearch#4085)

Gateway hygiene pre-compression only checked model.context_length from
the top-level config, missing per-model context_length defined in
custom_providers entries. This caused premature compression for custom
provider users (e.g. 128K default instead of 200K configured).

The AIAgent's own compressor already reads custom_providers correctly
(run_agent.py lines 1171-1189). This adds the same fallback to the
gateway hygiene path, running after runtime provider resolution so
the base_url is available for matching.
Display a brief status message before the heavy agent initialization
(OpenAI client setup, tool loading, memory init, etc.) so users
aren't staring at a blank screen for several seconds.

Only prints when self.agent is None (first use or after model switch).

Closes NousResearch#4060

Co-authored-by: SHL0MS <SHL0MS@users.noreply.github.com>
Add should_use_color() function to hermes_cli/colors.py that checks
NO_COLOR (https://no-color.org/) and TERM=dumb before emitting ANSI
escapes. The existing color() helper now uses this function instead
of a bare isatty() check.

This is the foundation — cli.py and banner.py still have inline ANSI
constants that bypass this module (tracked in NousResearch#4071).

Closes NousResearch#4066

Co-authored-by: SHL0MS <SHL0MS@users.noreply.github.com>
…NousResearch#4087)

Reference docs fixes:
- cli-commands.md: remove non-existent --provider alibaba, add hermes
  profile/completion/plugins/mcp to top-level table, add --profile/-p
  global flag, add --source chat option
- slash-commands.md: add /yolo and /commands, fix /q alias conflict
  (resolves to /queue not /quit), add missing aliases (/bg, /set-home,
  /reload_mcp, /gateway)
- toolsets-reference.md: fix hermes-api-server (not same as hermes-cli,
  omits clarify/send_message/text_to_speech)
- profile-commands.md: fix show name required not optional, --clone-from
  not --from, add --remove/--name to alias, fix alias path, fix export/
  import arg types, remove non-existent fish completion
- tools-reference.md: add EXA_API_KEY to web tools requires_env
- mcp-config-reference.md: add auth key for OAuth, tool name sanitization
- environment-variables.md: add EXA_API_KEY, update provider values
- plugins.md: remove non-existent ctx.register_command(), add
  ctx.inject_message()

Feature docs additions:
- security.md: add /yolo mode, approval modes (manual/smart/off),
  configurable timeout, expanded dangerous patterns table
- cron.md: add wrap_response config, [SILENT] suppression
- mcp.md: add dynamic tool discovery, MCP sampling support
- cli.md: add Ctrl+Z suspend, busy_input_mode, tool_preview_length
- docker.md: add skills/credential file mounting

Messaging platform docs:
- telegram.md: add webhook mode, DoH fallback IPs
- slack.md: add multi-workspace OAuth support
- discord.md: add DISCORD_IGNORE_NO_MENTION
- matrix.md: add MSC3245 native voice messages
- feishu.md: expand from 129 to 365 lines (encrypt key, verification
  token, group policy, card actions, media, rate limiting, markdown,
  troubleshooting)
- wecom.md: expand from 86 to 264 lines (per-group allowlists, media,
  AES decryption, stream replies, reconnection, troubleshooting)

Configuration docs:
- quickstart.md: add DeepSeek, Copilot, Copilot ACP providers
- configuration.md: add DeepSeek provider, Exa web backend, terminal
  env_passthrough/images, browser.command_timeout, compression params,
  discord config, security/tirith config, timezone, auxiliary models

21 files changed, ~1000 lines added
…y buffered events (NousResearch#4083)

When the Matrix adapter receives encrypted events it can't decrypt
(MegolmEvent), it now:

1. Requests the missing room key from other devices via
   client.request_room_key(event) instead of silently dropping the message

2. Buffers undecrypted events (bounded to 100, 5 min TTL) and retries
   decryption after each E2EE maintenance cycle when new keys arrive

3. Auto-trusts/verifies all devices after key queries so other clients
   share session keys with the bot proactively

4. Exports Megolm keys on disconnect and imports them on connect, so
   session keys survive gateway restarts

This addresses the 'could not decrypt event' warnings that caused the
bot to miss messages in encrypted rooms.
Documents the Telegram webhook mode from NousResearch#3880:
- New 'Webhook Mode' section in telegram.md with polling vs webhook
  comparison, config table, Fly.io deployment example, troubleshooting
- Add TELEGRAM_WEBHOOK_URL/PORT/SECRET to environment-variables.md
- Add Telegram section to .env.example (existing + webhook vars)

Co-authored-by: raulbcs <raulbcs@users.noreply.github.com>
Co-authored-by: Yabuku-xD <78594762+Yabuku-xD@users.noreply.github.com>
…sResearch#4107)

Add github.repository checks to docker-publish and deploy-site
workflows so they skip on forks where upstream-specific resources
(Docker Hub org, custom domain) are unavailable.

Co-authored-by: StreamOfRon <StreamOfRon@users.noreply.github.com>
…ch#4100)

After migrating from OpenClaw, leftover workspace directories contain
state files (todo.json, sessions, logs) that confuse the agent — it
discovers them and reads/writes to stale locations instead of the
Hermes state directory, causing issues like cron jobs reading a
different todo list than interactive sessions.

Changes:
- hermes claw migrate now offers to archive the source directory after
  successful migration (rename to .pre-migration, not delete)
- New `hermes claw cleanup` subcommand for users who already migrated
  and need to archive leftover OpenClaw directories
- Migration notes updated with explicit cleanup guidance
- 42 tests covering all new functionality

Reported by SteveSkedasticity — multiple todo.json files across
~/.hermes/, ~/.openclaw/workspace/, and ~/.openclaw/workspace-assistant/
caused cron jobs to read from wrong locations.
…usResearch#4093)

* fix: treat non-sk-ant- prefixed keys (Azure AI Foundry) as regular API keys, not OAuth tokens

* fix: treat non-sk-ant- keys as regular API keys, not OAuth tokens

_is_oauth_token() returned True for any key not starting with
sk-ant-api, misclassifying Azure AI Foundry keys as OAuth tokens
and sending Bearer auth instead of x-api-key → 401 rejection.

Real Anthropic OAuth tokens all start with sk-ant-oat (confirmed
from live .credentials.json). Non-sk-ant- keys are third-party
provider keys that should use x-api-key.

Test fixtures updated to use realistic sk-ant-oat01- prefixed
tokens instead of fake strings.

Salvaged from PR NousResearch#4075 by @HangGlidersRule.

---------

Co-authored-by: Clawdbot <clawdbot@openclaw.ai>
…n refresh (NousResearch#4126)

Claude Code >=2.1.81 checks for a 'scopes' array containing 'user:inference'
in ~/.claude/.credentials.json before accepting stored OAuth tokens as valid.

When Hermes refreshes the token, it writes only accessToken, refreshToken, and
expiresAt — omitting the scopes field. This causes Claude Code to report
'loggedIn: false' and refuse to start, even though the token is valid.

This commit:
- Parses the 'scope' field from the OAuth refresh response
- Passes it to _write_claude_code_credentials() as a keyword argument
- Persists the scopes array in the claudeAiOauth credential store
- Preserves existing scopes when the refresh response omits the field

Tested against Claude Code v2.1.87 on Linux — auth status correctly reports
loggedIn: true and claude --print works after this fix.

Co-authored-by: Nick <git@flybynight.io>
… top-level (NousResearch#4116)

Major reorganization of the documentation site for better discoverability
and navigation. 94 pages across 8 top-level sections (was 5).

Structural changes:
- Promote Features from 3-level-deep subcategory to top-level section
  with new Overview hub page categorizing all 26 feature pages
- Promote Messaging Platforms from User Guide subcategory to top-level
  section, add platform comparison matrix (13 platforms x 7 features)
- Create new Integrations section with hub page, grouping MCP, ACP,
  API Server, Honcho, Provider Routing, Fallback Providers
- Extract AI provider content (626 lines) from configuration.md into
  dedicated integrations/providers.md — configuration.md drops from
  1803 to 1178 lines
- Subcategorize Developer Guide into Architecture, Extending, Internals
- Rename "User Guide" to "Using Hermes" for top-level items

Orphan fixes (7 pages now reachable via sidebar):
- build-a-hermes-plugin.md added to Guides
- sms.md added to Messaging Platforms
- context-references.md added to Features > Core
- plugins.md added to Features > Core
- git-worktrees.md added to Using Hermes
- checkpoints-and-rollback.md added to Using Hermes
- checkpoints.md (30-line stub) deleted, superseded by
  checkpoints-and-rollback.md (203 lines)

New files:
- integrations/index.md — Integrations hub page
- integrations/providers.md — AI provider setup (extracted)
- user-guide/features/overview.md — Features hub page

Broken link fixes:
- quickstart.md, faq.md: update context-length-detection anchors
- configuration.md: update checkpoints link
- overview.md: fix checkpoint link path

Docusaurus build verified clean (zero broken links/anchors).
…ression

When context compression fires during run_conversation() in the gateway,
the compressed messages were silently lost on the next turn. Two bugs:

1. Agent-side: _flush_messages_to_session_db() calculated
   flush_from = max(len(conversation_history), _last_flushed_db_idx).
   After compression, _last_flushed_db_idx was correctly reset to 0,
   but conversation_history still had its original pre-compression
   length (e.g. 200). Since compressed messages are shorter (~30),
   messages[200:] was empty — nothing written to the new session's
   SQLite.

   Fix: Set conversation_history = None after each _compress_context()
   call so start_idx = 0 and all compressed messages are flushed.

2. Gateway-side: history_offset was always len(agent_history) — the
   original pre-compression length. After compression shortened the
   message list, agent_messages[200:] was empty, causing the gateway
   to fall back to writing only a user/assistant pair, losing the
   compressed summary and tail context.

   Fix: Detect session splits (agent.session_id != original) and set
   history_offset = 0 so all compressed messages are written to JSONL.
Wire the existing tool_progress_callback through the API server's
streaming handler so Open WebUI users see what tool is running.

Uses the existing 3-arg callback signature (name, preview, args)
that fires at tool start — no changes to run_agent.py needed.
Progress appears as inline markdown in the SSE content stream.

Inspired by PR NousResearch#4032 by sroecker, reimplemented to avoid breaking
the callback signature used by CLI and gateway consumers.
…ck (NousResearch#4129)

Co-authored-by: Maymun <139681654+maymuneth@users.noreply.github.com>
Remove [:8] truncation from hermes cron list output. Job IDs are 12
hex chars — truncating to 8 makes them unusable for cron run/pause/remove
which require the full ID.

Co-authored-by: vitobotta <vitobotta@users.noreply.github.com>
…arch#4133)

* fix(alibaba): use standard DashScope international endpoint

The Alibaba Cloud provider was hardcoded to the coding-intl endpoint
(https://coding-intl.dashscope.aliyuncs.com/v1) which only accepts
Alibaba Coding Plan API keys.

Standard DashScope API keys fail with invalid_api_key error against
this endpoint. Changed to the international compatible-mode endpoint
(https://dashscope-intl.aliyuncs.com/compatible-mode/v1) which works
with standard DashScope keys.

Users with Coding Plan keys or China-region keys can still override
via DASHSCOPE_BASE_URL or config.yaml base_url.

Fixes NousResearch#3912

* fix: update test to match new DashScope default endpoint

---------

Co-authored-by: kagura-agent <kagura.chen28@gmail.com>
)

Auto-compression still runs silently in the background with server-side
logging, but no longer sends messages to the user's chat about it.

Removed:
- 'Session is large... Auto-compressing' pre-compression notification
- 'Compressed: N → M messages' post-compression notification
- 'Session is still very large after compression' warning
- 'Auto-compression failed' warning
- Rate-limit tracking (only existed for these warnings)
…ousResearch#4138)

Update docs to reflect that tool progress now streams inline during
SSE responses. Previously docs said tool calls were invisible.

- api-server.md: add 'Tool progress in streams' note to streaming docs
- open-webui.md: update 'How It Works' steps, add Tool Progress tip
teknium1 and others added 30 commits April 3, 2026 15:00
…iour

The '📨 Queued:' label was misleading — it looked like the message was
silently deferred when it was actually being sent immediately after the
interrupt. Changed to '⚡ Sending after interrupt:' with multi-message
count when the user typed several messages during agent execution.

Added comment documenting that this code path only applies when
busy_input_mode == 'interrupt' (the default).

Based on PR NousResearch#4821 by iRonin.

Co-authored-by: iRonin <iRonin@users.noreply.github.com>
Fix double file read bug in google_api.py _missing_scopes(), consolidate
redundant _normalize_scope_values into callers, merge duplicate except blocks.
…to terminal_tool

- Add contextvars.Token[str] type hints to set/reset_current_session_key
- Use get_current_session_key(default='') in terminal_tool.py for background
  process session tracking, fixing the same env var race for concurrent
  gateway sessions spawning background processes
…opics

Reads config.extra['group_topics'] to bind skills to specific thread_ids
in supergroup/forum chats. Mirrors the dm_topics skill injection pattern
but for group chat_type. Enables per-topic skill auto-loading in Falcon HQ.

Config format:
  platforms.telegram.extra.group_topics:
    - chat_id: -1003853746818
      topics:
        - name: FalconConnect
          thread_id: 5
          skill: falconconnect-architecture
- 7 new tests covering skill binding, fallthrough, coercion
- Docs section in telegram.md with config format, field reference,
  comparison table, and thread_id discovery tip
…ls (NousResearch#4918)

The Anthropic SDK appends /v1/messages to the base_url, so OpenCode's
base URL https://opencode.ai/zen/go/v1 produced a double /v1 path
(https://opencode.ai/zen/go/v1/v1/messages), causing 404s for MiniMax
models. Strip trailing /v1 when api_mode is anthropic_messages.

Also adds MiMo-V2-Pro, MiMo-V2-Omni, and MiniMax-M2.5 to the OpenCode
Go model lists per their updated docs.

Fixes NousResearch#4890
NousResearch#4878)

* fix(gateway): add message deduplication to Discord and Slack adapters (NousResearch#4777)

Discord RESUME replays events after reconnects (~7/day observed),
and Slack Socket Mode can redeliver events if the ack was lost.
Neither adapter tracked which messages were already processed,
causing duplicate bot responses.

Add _seen_messages dedup cache (message ID → timestamp) with 5-min
TTL and 2000-entry cap to both adapters, matching the pattern already
used by Mattermost, Matrix, WeCom, Feishu, DingTalk, and Email.

The check goes at the very top of the message handler, before any
other logic, so replayed events are silently dropped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prevent duplicate messages on partial stream delivery

When streaming fails after tokens are already delivered to the platform,
_interruptible_streaming_api_call re-raised the error into the outer
retry loop, which would make a new API call — creating a duplicate
message.

Now checks deltas_were_sent before re-raising: if partial content was
already streamed, returns a stub response instead. The outer loop treats
the turn as complete (no retry, no fallback, no duplicate).

Inspired by PR NousResearch#4871 (@trevorgordon981) which identified the bug.
This implementation avoids monkey-patching exception objects and keeps
the fix within the streaming call boundary.

---------

Co-authored-by: Mibayy <mibayy@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ands (NousResearch#4926)

The base adapter's active-session guard queues all messages when an agent
is running. This creates a deadlock for /approve and /deny: the agent
thread is blocked on threading.Event.wait() in tools/approval.py waiting
for resolve_gateway_approval(), but the /approve command is queued waiting
for the agent to finish.

Dispatch /approve and /deny directly to the message handler (which routes
to gateway/run.py's _handle_approve_command) without going through
_process_message_background — avoids spawning a competing background task
that would mess with session lifecycle/guards.

Fixes NousResearch#4898
Co-authored-by: mechovation (original diagnosis in PR NousResearch#4904)
Input like /Users/ironin/file.md:45-46 was routed to process_command()
because it starts with /. Added _looks_like_slash_command() which checks
whether the first word contains additional / characters — commands never
do (/help, /model), paths always do (/Users/foo/bar.md).

Applied to both process_loop routing and handle_enter interrupt bypass.
Preserves prefix matching (/h → /help) since short prefixes still pass
the check.

Based on PR NousResearch#4782 by iRonin.

Co-authored-by: iRonin <iRonin@users.noreply.github.com>
…ess modes (NousResearch#4935)

The tool_preview_length: 0 (unlimited) config change from e314833
removed truncation from gateway progress messages in all/new modes.
This caused full terminal commands, code blocks, and file paths to
appear as permanent messages in Telegram -- the old 40-char truncation
was the correct behavior for messaging platforms.

Now:
- all/new modes: always truncate previews to 40 chars (old behavior)
- verbose mode: respects tool_preview_length config for JSON args cap

Reported by Paulclgro and socialsurfer on Discord.
…search#4825)

OpenViking is multi-tenant and requires X-OpenViking-Account and
X-OpenViking-User headers. Without them, API calls like POST
/api/v1/search/find fail on authenticated servers.

Add both headers to _VikingClient._headers(), read from env vars
OPENVIKING_ACCOUNT (default: root) and OPENVIKING_USER (default:
default). All instantiation sites inherit the fix automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When mem0.json exists but is missing the api_key (e.g. after running
`hermes memory setup`), the plugin reports "not available" even though
MEM0_API_KEY is set in .env.  This happens because _load_config()
returns the JSON file contents verbatim, never falling back to env vars.

Use env vars as the base config and let mem0.json override individual
keys on top, so both config sources work together.

Fixes: mem0 plugin shows "not available" despite valid MEM0_API_KEY in .env
…merge

The original filter (if v) silently drops False and 0, so
'rerank: false' in mem0.json would be ignored. Use explicit
None/empty-string check to preserve intentional falsy values.
…esearch#4940)

When a skill is active, user_message contains the full SKILL.md content
injected by the skill system. This bloated string was being passed to
memory provider sync_all(), queue_prefetch_all(), and prefetch_all(),
causing providers with query size limits (e.g. Honcho's 10K char limit)
to fail.

Both call sites now use original_user_message (the clean user input,
already defined at line 6516) instead of the skill-inflated user_message:

- Pre-turn prefetch (line ~6695): prefetch_all() query
- Post-turn sync (line ~8672): sync_all() + queue_prefetch_all()

Fixes NousResearch#4889
The config key skills.external_dirs and core resolution (get_all_skills_dirs,
get_external_skills_dirs in agent/skill_utils.py) already existed but several
code paths still only scanned SKILLS_DIR. Now external dirs are respected
everywhere:

- skills_categories(): scan all dirs for category discovery
- _get_category_from_path(): resolve categories against any skills root
- skill_manager_tool._find_skill(): search all dirs for edit/patch/delete
- credential_files.get_skills_directory_mount(): mount all dirs into
  Docker/Singularity containers (external dirs at external_skills/<idx>)
- credential_files.iter_skills_files(): list files from all dirs for
  Modal/Daytona upload
- tools/environments/ssh.py: rsync all skill dirs to remote hosts
- gateway _check_unavailable_skill(): check disabled skills across all dirs

Usage in config.yaml:
  skills:
    external_dirs:
      - ~/repos/agent-skills/hermes
      - /shared/team-skills
Clearer workflow with validation/chunking steps, expanded description
with trigger terms for better agent matching, tightened error handling.
Fixed stray pipe character in original PR diff.

Based on PR NousResearch#4778 by fernandezbaptiste.

Co-authored-by: fernandezbaptiste <fernandezbaptiste@users.noreply.github.com>
…ionable error (NousResearch#4959)

- HERMES_AGENT_TIMEOUT=0 now means no limit (infinite execution)
- Periodic 'still working' notifications every 10 minutes for long tasks
- Timeout error message now tells users how to increase the limit
- Stale-lock eviction handles infinite timeout correctly (float inf TTL)
…ile read guard

Two pre-existing issues causing test_file_read_guards timeouts on CI:

1. agent/redact.py: _ENV_ASSIGN_RE used unbounded [A-Z_]* with
   IGNORECASE, matching any letter/underscore to end-of-string at
   each position → O(n²) backtracking on 100K+ char inputs.
   Bounded to {0,50} since env var names are never that long.

2. tools/file_tools.py: redact_sensitive_text() ran BEFORE the
   character-count guard, so oversized content (that would be rejected
   anyway) went through the expensive regex first. Reordered to check
   size limit before redaction.
Add MiniMax as a fifth TTS provider alongside Edge TTS, ElevenLabs,
OpenAI, and NeuTTS. Supports speech-2.8-hd (recommended default) and
speech-2.8-turbo models via the MiniMax T2A HTTP API.

Changes:
- Add _generate_minimax_tts() with hex-encoded audio decoding
- Add MiniMax to provider dispatch, requirements check, and Telegram
  Opus compatibility handling
- Add MiniMax to interactive setup wizard with API key prompt
- Update TTS documentation and config example

Configuration:
  tts:
    provider: "minimax"
    minimax:
      model: "speech-2.8-hd"
      voice_id: "English_Graceful_Lady"

Requires MINIMAX_API_KEY environment variable.

API reference: https://platform.minimax.io/docs/api-reference/speech-t2a-http
* feat: add /branch (/fork) command for session branching

Inspired by Claude Code's /branch command. Creates a copy of the current
session's conversation history in a new session, allowing the user to
explore a different approach without losing the original.

Works like 'git checkout -b' for conversations:
- /branch            — auto-generates a title from the parent session
- /branch my-idea    — uses a custom title
- /fork              — alias for /branch

Implementation:
- CLI: _handle_branch_command() in cli.py
- Gateway: _handle_branch_command() in gateway/run.py
- CommandDef with 'fork' alias in commands.py
- Uses existing parent_session_id field in session DB
- Uses get_next_title_in_lineage() for auto-numbered branches
- 14 tests covering session creation, history copy, parent links,
  title generation, edge cases, and agent sync

* fix: clear ghost status-bar lines on terminal resize

When the terminal shrinks (e.g. un-maximize), the emulator reflows
previously full-width rows (status bar, input rules) into multiple
narrower rows. prompt_toolkit's _on_resize only cursor_up()s by the
stored layout height, missing the extra rows from reflow — leaving
ghost duplicates of the status bar visible.

Fix: monkey-patch Application._on_resize to detect width shrinks,
calculate the extra rows created by reflow, and inflate the renderer's
cursor_pos.y so the erase moves up far enough to clear ghosts.
OAuth refresh tokens are single-use. When multiple consumers share the
same Anthropic OAuth session (credential pool entries, Claude Code CLI,
multiple Hermes profiles), whichever refreshes first invalidates the
refresh token for all others. This causes a cascade:

1. Pool entry tries to refresh with a consumed refresh token → 400
2. Pool marks the credential as "exhausted" with a 24-hour cooldown
3. All subsequent heartbeats skip the credential entirely
4. The fallback to resolve_anthropic_token() only works while the
   access token in ~/.claude/.credentials.json hasn't expired
5. Once it expires, nothing can auto-recover without manual re-login

Fix:
- Add _sync_anthropic_entry_from_credentials_file() to detect when
  ~/.claude/.credentials.json has a newer refresh token and sync it
  into the pool entry, clearing exhaustion status
- After a successful pool refresh, write the new tokens back to
  ~/.claude/.credentials.json so other consumers stay in sync
- On refresh failure, check if the credentials file has a different
  (newer) refresh token and retry once before marking exhausted
- In _available_entries(), sync exhausted claude_code entries from
  the credentials file before applying the 24-hour cooldown, so a
  manual re-login or external refresh immediately unblocks agents

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback: replace bare `except: pass` with a debug
log when the post-retry write-back to ~/.claude/.credentials.json
fails. The write-back is best-effort (token is already resolved),
but logging helps troubleshooting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…les (NousResearch#4738)

Add docker_env option to terminal config — a dict of key-value pairs that
get set inside Docker containers via -e flags at both container creation
(docker run) and per-command execution (docker exec) time.

This complements docker_forward_env (which reads values dynamically from
the host process environment). docker_env is useful when Hermes runs as a
systemd service without access to the user's shell environment — e.g.
setting SSH_AUTH_SOCK or GNUPGHOME to known stable paths for SSH/GPG
agent socket forwarding.

Precedence: docker_env provides baseline values; docker_forward_env
overrides for the same key.

Config example:
  terminal:
    docker_env:
      SSH_AUTH_SOCK: /run/user/1000/ssh-agent.sock
      GNUPGHOME: /root/.gnupg
    docker_volumes:
      - /run/user/1000/ssh-agent.sock:/run/user/1000/ssh-agent.sock
      - /run/user/1000/gnupg/S.gpg-agent:/root/.gnupg/S.gpg-agent
Audit found 24+ discrepancies between docs and code. Fixed:

HIGH severity:
- Remove honcho toolset from tools-reference, toolsets-reference, and tools.md
  (converted to memory provider plugin, not a built-in toolset)
- Add note that Honcho is available via plugin

MEDIUM severity:
- Add hermes memory command family to cli-commands.md (setup/status/off)
- Add --clone-all, --clone-from to profile create in cli-commands.md
- Add --max-turns option to hermes chat in cli-commands.md
- Add /btw slash command to slash-commands.md
- Fix profile show example output (remove nonexistent disk usage,
  add .env and SOUL.md status lines)
- Add missing hermes-webhook toolset to toolsets-reference.md
- Add 5 missing providers to fallback-providers.md table
- Add 7 missing providers to providers.md fallback list
- Fix outdated model examples: glm-4-plus→glm-5, moonshot-v1-auto→kimi-for-coding
- Replaces auto_up/auto_down with unconditional history_previous/next
- auto_up/auto_down only triggered history at document boundaries (first/last
  line), so in multiline input (cursor mid-text), up/down just moved the
  cursor instead of browsing history — unexpected REPL behavior
- Fix: up/down now always browse history; Ctrl+up/Ctrl+down handle cursor
  movement within multiline text when needed
- Adds Ctrl+up and Ctrl+down bindings as escape hatch for multiline editing
Replaces auto_up/auto_down with explicit cursor position movement via
get_cursor_position_up/get_cursor_position_down + set_cursor_position.

Problem: auto_up/auto_down fall back to history browsing when cursor
is at the first/last character of the document. In a multiline prompt
where the cursor is mid-text, pressing up/down would unexpectedly
browse history instead of moving the cursor to the line above/below.

Fix: up/down now ALWAYS move cursor within the text. History browsing
is available via Ctrl+up / Ctrl+down as an escape hatch.

Files: cli.py (+47/-7 lines)
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.