hermes-bsd/agent
Teknium 1d00716754
fix(cli,tui): align CJK / wide-char markdown tables (#23863)
CJK and emoji glyphs render as two terminal cells but JS String#length
and the model's own padding count them as one, so any markdown table
with Chinese / Japanese / Korean cells drifts right per row when a
real terminal renders it. Both surfaces fix this with a display-cell
width measurement (wcswidth on the Python side, stringWidth on the
TUI side).

Changes:
- agent/markdown_tables.py: new helper. realign_markdown_tables(text)
  detects markdown table blocks (header + |---| divider) and
  rewrites the row padding using wcwidth.wcswidth so every pipe and
  dash lines up across rows. No-op on text without tables.
- cli.py: hook the helper into _render_final_assistant_content for
  strip / render modes (raw passes through untouched), and into the
  streaming line emitter so live token-by-token rendering also
  produces aligned tables. A small two-buffer state machine in
  _emit_stream_text holds table rows until the block ends, then
  flushes them through the realigner so all rows pad to a single
  per-column width.
- ui-tui/src/components/markdown.tsx: renderTable now uses
  stringWidth (Bun.stringWidth fast path + East-Asian-width-aware
  fallback, already memoised in @hermes/ink) instead of UTF-16
  String#length for both column-width measurement and per-cell
  padding. Drops the comment that documented the bug as a deliberate
  limitation.

Validation:
- New tests/agent/test_markdown_tables.py (11): every rebuilt block
  shares pipe column offsets across rows for pure CJK, mixed
  CJK+emoji, ragged-row, and multi-table inputs.
- Updated tests/cli/test_cli_markdown_rendering.py: the existing
  strip-mode test asserted exact whitespace; rewritten to assert the
  alignment contract (cell content survives + every rendered row
  shares pipe offsets).
- New ui-tui markdown.test.ts case (1): rendered column-2 start
  offset is identical for the header + every body row, including
  the CJK row that drifted before the fix.
- Live: hermes chat -q with the user-reported screenshot prompt now
  produces a perfectly aligned table on the wire (header, divider,
  4 body rows including '通义千问', all pipes at identical columns).
2026-05-11 11:13:06 -07:00
..
transports fix(xai): omit reasoning.effort for grok models that reject it (#23435) 2026-05-10 15:21:30 -07:00
__init__.py
account_usage.py
anthropic_adapter.py feat(computer-use): cua-driver backend, universal any-model schema 2026-05-08 11:07:38 -07:00
auxiliary_client.py fix(auxiliary): cache 402'd providers as unhealthy with TTL to stop per-call retry storms (#23597) 2026-05-10 22:43:14 -07:00
bedrock_adapter.py fix(bedrock): preserve reasoningContent across converse normalization 2026-05-07 05:17:16 -07:00
codex_responses_adapter.py feat(vision): vision_analyze returns pixels to vision-capable models, not aux text (#22955) 2026-05-09 21:06:19 -07:00
context_compressor.py chore: ruff auto-fixes — collapsible-else-if, if-stmt-min-max, dict.fromkeys (#23926) 2026-05-11 11:03:29 -07:00
context_engine.py
context_references.py
copilot_acp_client.py feat(cross-platform): psutil for PID/process management + Windows footgun checker 2026-05-08 14:27:40 -07:00
credential_pool.py fix(auth): shorten credential 401 cooldown 2026-05-07 06:15:33 -07:00
credential_sources.py
curator.py feat(curator): hint at hermes curator pin in the rename block (#23212) 2026-05-10 06:44:53 -07:00
curator_backup.py fix(curator): authoritative absorbed_into on delete + restore cron skill links on rollback (#18671) (#18731) 2026-05-02 01:29:57 -07:00
display.py feat(computer-use): background focus-safe backend — set_value, structured windows, MIME detection 2026-05-08 11:07:38 -07:00
error_classifier.py fix(error_classifier): classify generic-typed timeout messages as transient (carve-out of #22664) 2026-05-09 17:54:07 -07:00
file_safety.py
gemini_cloudcode_adapter.py
gemini_native_adapter.py fix(gemini): extract usageMetadata from streaming chunks for token tracking 2026-05-04 02:33:30 -07:00
gemini_schema.py
google_code_assist.py
google_oauth.py fix(google_oauth): close TOCTOU window when saving credentials 2026-05-04 03:16:19 -07:00
i18n.py feat(i18n): localize all gateway commands + web dashboard, add 8 new locales (16 total) (#22914) 2026-05-10 07:14:14 -07:00
image_gen_provider.py
image_gen_registry.py
image_routing.py fix(image-routing): sniff magic bytes for image MIME, ignore misleading suffix 2026-05-07 05:58:11 -07:00
insights.py
lmstudio_reasoning.py
manual_compression_feedback.py
markdown_tables.py fix(cli,tui): align CJK / wide-char markdown tables (#23863) 2026-05-11 11:13:06 -07:00
memory_manager.py fix: salvage batch — compaction guidance, memory authority, cache eviction after compression 2026-05-05 22:33:45 -07:00
memory_provider.py docs(agent): remove stale BuiltinMemoryProvider references from memory module docstrings 2026-05-05 13:33:49 -07:00
model_metadata.py fix(xai): omit reasoning.effort for grok models that reject it (#23435) 2026-05-10 15:21:30 -07:00
models_dev.py perf(models_dev): cache-first lookup, skip network when disk cache is fresh (#22808) 2026-05-09 13:32:38 -07:00
moonshot_schema.py fix(moonshot): also strip nullable/enum after anyOf collapse 2026-04-30 23:14:31 -07:00
nous_rate_guard.py codebase: add encoding='utf-8' to all bare open() calls (PLW1514) 2026-05-08 14:27:40 -07:00
onboarding.py
plugin_llm.py feat(plugins): run any LLM call from inside a plugin via ctx.llm (#23194) 2026-05-10 07:09:28 -07:00
prompt_builder.py docs(kanban): worker lane contract page + review-required convention 2026-05-10 18:15:52 -07:00
prompt_caching.py
rate_limit_tracker.py
redact.py feat(security): enable secret redaction by default (#17691, #20785) (#21193) 2026-05-07 05:10:33 -07:00
retry_utils.py
shell_hooks.py codebase: add encoding='utf-8' to all bare open() calls (PLW1514) 2026-05-08 14:27:40 -07:00
skill_commands.py fix(skills): rescan skill_commands cache when platform scope changes (#18739) 2026-05-02 01:36:53 -07:00
skill_preprocessing.py
skill_utils.py perf(cli): cut ~19s from 'hermes' cold start (skills cache + lazy Feishu + no Nous HTTP) (#22138) 2026-05-08 16:39:32 -07:00
subdirectory_hints.py
think_scrubber.py fix(agent): stateful streaming scrubber for reasoning-block leaks (#17924) (#20184) 2026-05-05 04:33:38 -07:00
title_generator.py fix: improve telegram topic mode setup 2026-05-04 12:07:17 -07:00
tool_guardrails.py
trajectory.py
usage_pricing.py fix(analytics): prevent silent token loss and add Claude 4.5–4.7 pricing (#21455) 2026-05-07 13:24:31 -07:00