The hermes-agent CLI KawaiiSpinner sends decorative kaomoji text
like "(◕‿◕✿) pondering..." through thinking_callback for its TUI
widget. The bridge forwarded this as thinking.delta events, which
the frontend stored in the message reasoning field.
Over long conversations this contaminated the model's context:
_copy_reasoning_content_for_api promoted the kaomoji text to
reasoning_content, causing the LLM to reproduce kaomoji patterns
in a self-reinforcing degradation loop.
Fix: _make_thinking_callback unconditionally sends empty text.
thinking_callback is purely CLI spinner status — it has no place in
conversation history. Actual model reasoning (reasoning.delta) is
unaffected.
* fix(bridge): refresh terminal env from profile config on profile switch
Profile switching changes HERMES_HOME but the TERMINAL_* environment
variables (TERMINAL_ENV, TERMINAL_SSH_HOST, etc.) still point to the
root config's terminal settings set at gateway startup.
Add _refresh_terminal_env() that re-reads terminal config from the
active profile's config.yaml and sets the corresponding TERMINAL_* env
vars. Call it in:
- AgentPool.get_or_create(): when creating a session for a profile
- AgentPool._run_chat(): before agent execution, inside _profile_env
Errors are handled gracefully: YAML parse failures log to stderr,
terminal_tool cache invalidation failures are silently ignored, and
missing config files are skipped without error.
* fix(bridge): refresh terminal env in worker profile setup
_set_worker_profile_env() handles broker-spawned worker subprocesses
that are isolated per profile. The worker inherits TERMINAL_* env vars
from the broker (root config), and _profile_env() is a no-op in worker
mode, so terminal config was never refreshed for non-default profiles.
Adding _refresh_terminal_env() here means each worker subprocess reads
its own profile's config.yaml terminal section on startup, solving
profile-isolated terminal backends (e.g. SSH per profile).
* fix bridge terminal env refresh scope
* refresh worker profile env for new agents
* avoid bridge worker restart for channel config
* align config controller bridge restart tests
---------
Co-authored-by: GoldenFish123321 <goldfishx@gmail.com>
Co-authored-by: GoldenFishX <golden_fish@foxmail.com>
* fix: don't drop pending tool-call-marker prefix on tool.started/run.done
The `filterBridgeToolCallMarkupDelta` filter holds back any text that
ends in a partial prefix of `[Calling tool:` (i.e. `[`, `[C`, `[Ca`,
..., `[Calling tool`) so it can decide whether the buffered chars are
the start of a tool-call markup block to be hidden, or just regular
text to be released by the next delta.
The bug: that "release on next delta" assumption breaks at TWO points:
1. **On `tool.started`**: the next chunk for this assistant message is
the tool call itself, NOT a follow-up text delta. Buffered chars
sit there forever and nothing flushes them — they vanish silently
from the user-visible stream.
2. **On run completion**: the code did
`state.bridgePendingToolCallMarkup = undefined` directly, dropping
any pending chars without forwarding them.
Both cases produce the user-visible symptom of "abrupt cuts in text
right before/after tool calls (terminal, read_file, write_file...)" —
1 to 13 characters disappear at exactly the boundary where the model
was emitting natural prose that happened to end with `[`.
The fix introduces `flushPendingToolCallMarkup(state)` and calls it:
- In the `tool.started` branch BEFORE recording the tool call, so the
buffered chars are appended to the open assistant message and emitted
as a normal `message.delta` to the client.
- At run-done BEFORE clearing the buffer, same flush path.
This is a pure recovery patch — no change to the marker detection
logic itself. If the buffer turns out to actually be a real
`[Calling tool: ...]` marker that just hasn't completed yet, that
case is still caught by the existing `markerIdx >= 0` branch in the
filter on the next delta. The only behavioral change is that the
"orphan" cases (text that ends with `[` but never becomes a marker)
are no longer dropped.
* fix bridge marker flush persistence
---------
Co-authored-by: Paulo Cavallari <paulocavallari@users.noreply.github.com>
* Allow bridge sessions to run concurrently
* Stabilize bridge concurrency test
* Set bridge approval timeout to 120 seconds
* harden bridge approval concurrency
---------
Co-authored-by: Codex <codex@openai.com>
* fix: recursive skill scan for nested sub-category directories
The Web UI skill scanner (scanSkillsDir) only checked one level deep:
skills/<category>/<subdir>/SKILL.md. Sub-category containers like
mlops/evaluation/ (which has DESCRIPTION.md + subdirs but no SKILL.md)
were skipped entirely, hiding all 12 nested skills under mlops.
Changes:
- scanSkillsDir: extract collectSkills() recursive helper that depth-first
searches for SKILL.md at every level under a category directory.
Directories without SKILL.md but with subdirectories are recursively
descended into.
- listFiles handler: replace hardcoded join(category, skill) path with
recursive findSkillDir() search, so nested skill file browsing works
(e.g., mlops/evaluation/lm-evaluation-harness).
Fixes mlops category showing 1 skill instead of 13. All 20 other
categories verified with zero regression.
* fix: pA handler also needs recursive search for nested skill file content
The readFile_ (pA) handler was constructing direct paths like
skills/category/skill/... which fails for nested sub-category
skills (mlops/evaluation/lm-evaluation-harness). Added fallback
recursive search when direct path returns 404.
Also fixed listFiles (sA) handler to use recursive search for
the same reason - previous fix to dist was not in source TS.
Verified:
- lm-evaluation-harness SKILL.md content: 200 ✅
- vllm SKILL.md: 200 ✅
- huggingface-hub (non-nested): 200 ✅
- reference file in nested skill: 200 ✅
* fix: pA handler also needs recursive search for nested skill file content
The readFile_ (pA) handler was constructing direct paths like
skills/category/skill/... which fails for nested sub-category
skills (mlops/evaluation/lm-evaluation-harness). Added fallback
recursive search when direct path returns 404.
Also fixed listFiles (sA) handler to use recursive search for
the same reason - previous fix to dist was not in source TS.
Verified:
- lm-evaluation-harness SKILL.md content: 200 ✅
- vllm SKILL.md: 200 ✅
- huggingface-hub (non-nested): 200 ✅
- reference file in nested skill: 200 ✅
* harden recursive skill lookup
---------
Co-authored-by: gs <gs@localhost>
Co-authored-by: gutanulaif <gutanulaifa@gmail.com>