mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
fix(cli): the glyph layout's reasoning summary never repeats the start of the thought
The "∴ Thought for Ns" line that closes a reasoning block appended the reasoning's first bold heading, or — with no heading — its first sentence. Models whose reasoning has no headings (e.g. Claude) therefore got their opening sentence printed twice: once in the reasoning body streamed above the line, and again as the summary's title. Drop the title entirely; the reasoning is already shown in full right above, so the summary is just the duration. Removes the now-dead _full_title/thought_title/_TITLE_MAX helpers and the write-only _reasoning_full_text buffer in the stream mixin.
This commit is contained in:
1 parent
9ef7055f5b
commit
aa28a71341
3 files changed
+12
-31
No files matched your search
@@ -21,7 +21,6 @@ logger = logging.getLogger(__name__)
|
||||
_SPIN = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
||||
_RST, _BOLD, _DIM = "\x1b[0m", "\x1b[1m", "\x1b[2m"
|
||||
_RED = "\x1b[38;2;239;83;80m"
|
||||
_TITLE_MAX = 72
|
||||
|
||||
|
||||
def is_modern(cli) -> bool:
|
||||
@@ -112,28 +111,11 @@ def answer_label(timestamp: str = "") -> str:
|
||||
return f"\n{separator(True)}\n{_fg(_skin_hex('ui_accent', '#FFBF00'), bold=True)}{glyph} {name}{_RST}{stamp}"
|
||||
|
||||
|
||||
def _full_title(text: str) -> str:
|
||||
"""The reasoning's first ``**bold**`` heading, else its first sentence, whitespace-collapsed."""
|
||||
text = (text or "").strip()
|
||||
match = re.search(r"\*\*(.+?)\*\*", text)
|
||||
title = match.group(1) if match else re.split(r"(?<=[.!?])\s|\n", text, maxsplit=1)[0]
|
||||
return " ".join(title.split()).strip("*# ")
|
||||
|
||||
|
||||
def thought_title(text: str) -> str:
|
||||
"""Short title for a reasoning block: its first ``**bold**`` heading, else its first sentence."""
|
||||
title = _full_title(text)
|
||||
return title if len(title) <= _TITLE_MAX else title[:_TITLE_MAX - 1].rstrip() + "…"
|
||||
|
||||
|
||||
def thought_line(seconds: float | None, text: str) -> str:
|
||||
"""``∴ Thought for 3s · Planning the change`` (dim italic), closing a reasoning block."""
|
||||
# The reasoning is printed above this line; a title that IS the whole reasoning would repeat it.
|
||||
whole = _full_title(text) == " ".join((text or "").split()).strip("*# ")
|
||||
title = "" if whole else thought_title(text)
|
||||
def thought_line(seconds: float | None) -> str:
|
||||
"""``∴ Thought for 3s`` (dim italic), closing a reasoning block. No title: the reasoning is
|
||||
printed right above, so any excerpt of it would repeat what the user just read."""
|
||||
took = t("cli.modern.thought_for", seconds=f"{max(seconds or 0.0, 0.0):.0f}") if seconds else t("cli.modern.thought")
|
||||
body = f"{took} · {title}" if title else took
|
||||
return f"\x1b[2;3m∴ {body}{_RST}"
|
||||
return f"\x1b[2;3m∴ {took}{_RST}"
|
||||
|
||||
|
||||
_REASONING_PREVIEW_LINES = 10 # classic's post-hoc reasoning box shows this many unless /reasoning full
|
||||
@@ -152,7 +134,7 @@ def thought_rows(seconds: float | None, text: str, full: bool) -> list[str]:
|
||||
rows = ["", separator(False), *(reasoning_text_line(line) for line in shown)]
|
||||
if len(shown) < len(lines):
|
||||
rows.append(f"{_DIM} {t('cli.chat.reasoning_more_lines', count=len(lines) - len(shown))}{_RST}")
|
||||
return [*rows, thought_line(seconds, text), closing_separator(False)]
|
||||
return [*rows, thought_line(seconds), closing_separator(False)]
|
||||
|
||||
|
||||
def completed_tool_line(cli, function_name: str, args: dict | None, duration: float, result=None,
|
||||
|
||||
@@ -273,9 +273,8 @@ class CLIStreamMixin:
|
||||
from hermes_cli.cli_modern_layout import reasoning_text_line, separator
|
||||
if not getattr(self, "_reasoning_box_opened", False):
|
||||
self._reasoning_box_opened, self._reasoning_started_at = True, time.monotonic()
|
||||
self._reasoning_buf, self._reasoning_full_text = "", ""
|
||||
self._reasoning_buf = ""
|
||||
_cprint("\n" + separator(False))
|
||||
self._reasoning_full_text = getattr(self, "_reasoning_full_text", "") + text
|
||||
self._reasoning_buf = getattr(self, "_reasoning_buf", "") + text
|
||||
# Live, as classic: complete lines now, long partial lines force-flushed.
|
||||
while "\n" in self._reasoning_buf:
|
||||
@@ -331,10 +330,9 @@ class CLIStreamMixin:
|
||||
if getattr(self, "_reasoning_buf", ""):
|
||||
_cprint(reasoning_text_line(self._reasoning_buf))
|
||||
started = getattr(self, "_reasoning_started_at", 0.0)
|
||||
_cprint(thought_line(time.monotonic() - started if started else None,
|
||||
getattr(self, "_reasoning_full_text", "")))
|
||||
_cprint(thought_line(time.monotonic() - started if started else None))
|
||||
_cprint(closing_separator(False))
|
||||
self._reasoning_buf, self._reasoning_full_text, self._reasoning_box_opened = "", "", False
|
||||
self._reasoning_buf, self._reasoning_box_opened = "", False
|
||||
if not getattr(self, "_stream_box_live", False):
|
||||
self._release_held_status_lines()
|
||||
return
|
||||
|
||||
@@ -38,7 +38,8 @@ def test_glyph_layout_scrollback_is_width_independent(monkeypatch, modern):
|
||||
assert narrow == wide and not boxed, wide
|
||||
rows = "\n".join(wide).split("\n")
|
||||
thought = [line for line in rows if line.startswith("∴")]
|
||||
assert len(thought) == 1 and "Planning the change" in thought[0], rows
|
||||
# The summary never excerpts the reasoning (printed in full right above it): bare "∴ Thought for Ns".
|
||||
assert len(thought) == 1 and "·" not in thought[0], rows
|
||||
# Reasoning streams in full like classic, under a fixed-width dim bar (never re-wraps on resize)
|
||||
# and closed by the ∴ summary; the answer opens under its own bar.
|
||||
start = rows.index("**Planning the change**")
|
||||
@@ -50,8 +51,8 @@ def test_glyph_layout_scrollback_is_width_independent(monkeypatch, modern):
|
||||
|
||||
@pytest.mark.parametrize("reasoning", [
|
||||
"scripted reasoning about gamma zeta-reason",
|
||||
# Longer than the 72-char title cap: a shortened title would still repeat it.
|
||||
"scripted reasoning about gamma zeta-reason that keeps going well past the title length cap here",
|
||||
# A longer multi-sentence block: still never excerpted into the summary.
|
||||
"scripted reasoning about gamma zeta-reason that keeps going well past any short title length here",
|
||||
])
|
||||
def test_one_line_reasoning_is_shown_once(monkeypatch, reasoning):
|
||||
"""Reasoning streams above its ∴ summary, so a one-sentence block must not reappear as the title."""
|
||||
|
||||
Reference in new issue
Block a user