Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
aaff152
feat: Rich-based rendering engine with intra-line diff highlighting
KUSH42 Apr 1, 2026
bc45f30
fix(rich_output): Error token uses text colour not background
KUSH42 Apr 1, 2026
f0f7310
feat(rich_output): truncate DiffRenderer output at 80 lines
KUSH42 Apr 2, 2026
e3539af
feat(rich_output): syntax highlighting in diff output
KUSH42 Apr 2, 2026
74eb7ef
fix(rich_output): extend diff background to line numbers and sigils
KUSH42 Apr 2, 2026
ff70609
feat: syntax highlighting for tool outputs and LLM responses
KUSH42 Apr 1, 2026
bcca943
fix(rich_output): remove fence delimiters from Panel output
KUSH42 Apr 1, 2026
4cfeb2e
feat(rich_output): add line numbers to fenced code blocks
KUSH42 Apr 2, 2026
c73585a
fix(rich_output): use rich.markup.escape() to fix bracket/backslash r…
KUSH42 Apr 2, 2026
b627219
feat(rich_output): block-level and inline markdown rendering for LLM …
KUSH42 Apr 1, 2026
88787aa
fix(rich_output): blockquote rendering, link URLs, and format_respons…
KUSH42 Apr 1, 2026
2ec05a5
feat(rich_output): block-level markdown rendering (PR3)
KUSH42 Apr 1, 2026
01c9096
fix(rich_output): three correctness bugs in block markdown rendering
KUSH42 Apr 1, 2026
87b9ebf
fix(rich_output): blockquote rendering, link URLs, and format_respons…
KUSH42 Apr 1, 2026
cbebdf0
chore: remove spec and PR docs from branch
KUSH42 Apr 1, 2026
04e0aa7
fix(display): pass terminal width to DiffRenderer so diff truncation …
KUSH42 Apr 2, 2026
79637d0
fix(rich_output): _italic_ with spaces not rendering
KUSH42 Apr 2, 2026
9b89937
feat(rich_output): render <u> and <mark> HTML tags with correct neste…
KUSH42 Apr 2, 2026
586eeb7
feat(rich_output): complete inline markdown coverage
KUSH42 Apr 2, 2026
6d589df
fix(rich_output): nested underscore spans inside bold-star not rendering
KUSH42 Apr 2, 2026
83139ea
fix(rich_output): bold/italic not rendering on lines with inline code…
KUSH42 Apr 2, 2026
31d88e3
test(rich_output): expand coverage for inline formatting + code span …
KUSH42 Apr 2, 2026
09e1574
fix(cli): restore dim color after inline code spans in reasoning/thin…
KUSH42 Apr 2, 2026
6998e8f
feat(rich_output): stateful block markdown rendering (PR4)
KUSH42 Apr 2, 2026
5dc43c9
fix(rich_output): table column misalignment when cells contain inline…
KUSH42 Apr 2, 2026
b184e44
fix(rich_output): table column misalignment with wide/emoji characters
KUSH42 Apr 2, 2026
dc39fee
fix(tests): update link test — URL preserved in output
KUSH42 Apr 2, 2026
98bb310
fix(rich_output): OL items after '---' falsely become setext headings
KUSH42 Apr 2, 2026
9e8ee6f
feat(rich_output): bright-blue link color (_MD_LINK_ANSI)
KUSH42 Apr 2, 2026
c4df056
fix(rich_output): use truecolor for link color instead of ansibrightblue
KUSH42 Apr 2, 2026
1c89061
feat(rich_output): style bare https?:// URLs with link color
KUSH42 Apr 2, 2026
d7a02f8
feat(rich_output): extend bare URL detection to file://, ftp://, www.
KUSH42 Apr 2, 2026
5a199ba
fix(rich_output): bare URL regex captures ESC byte inside bold/italic…
KUSH42 Apr 2, 2026
58ba8fe
fix(rich_output): diff deletion line numbers diverge from new-file scale
KUSH42 Apr 2, 2026
91b24ce
feat(rich_output): support GFM optional boundary pipes in tables
KUSH42 Apr 2, 2026
bd5aac5
fix(rich_output): image reset code corrupts following markdown link
KUSH42 Apr 2, 2026
40f5435
feat(rich_output): ordered lists, task lists, nested blockquotes, set…
KUSH42 Apr 2, 2026
7e61f60
fix(rich_output): ref def title regex, 49 edge-case tests for new mar…
KUSH42 Apr 2, 2026
7e0e48a
feat(rich_output): strict GFM tables framed, fix loose table detection
KUSH42 Apr 2, 2026
d53b3e8
fix(rich_output): thread reset_suffix through apply_block_line to res…
KUSH42 Apr 3, 2026
aa51ce7
fix(rich_output): deletion line numbers diverge when context splits a…
KUSH42 Apr 3, 2026
c211925
feat: skin-aware code themes, /markdown toggle, fast-path detection, …
lucaspirola Apr 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 243 additions & 1 deletion agent/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,28 @@
_MAX_INLINE_DIFF_FILES = 6
_MAX_INLINE_DIFF_LINES = 80

# Set to True by the CLI when code-highlight mode is active. Consumed by
# get_cute_tool_message to suppress the inline code snippet (the highlighted
# block will show the full code immediately after).
_code_highlight_active: bool = False


def set_code_highlight_active(active: bool) -> None:
global _code_highlight_active
_code_highlight_active = active


# Rich-based rendering (syntax highlighting + enhanced diffs)
try:
from agent.rich_output import DiffRenderer as _RichDiffRenderer
from agent.rich_output import SyntaxHighlighter as _RichSyntaxHighlighter
from agent.rich_output import clean_command_output
_rich_diff = _RichDiffRenderer()
_rich_syntax = _RichSyntaxHighlighter()
_RICH_OUTPUT = True
except ImportError:
_RICH_OUTPUT = False


@dataclass
class LocalEditSnapshot:
Expand Down Expand Up @@ -411,7 +433,20 @@ def _emit_inline_diff(diff_text: str, print_fn) -> bool:


def _render_inline_unified_diff(diff: str) -> list[str]:
"""Render unified diff lines in Hermes' inline transcript style."""
"""Render unified diff lines with line numbers and coloured backgrounds.

Uses rich_output.DiffRenderer when available (line numbers, green/red
background highlights). Falls back to the original ANSI-string path.
"""
if _RICH_OUTPUT:
try:
import shutil
width = shutil.get_terminal_size().columns
return _rich_diff.to_lines(diff, width=width, max_lines=0)
except Exception as exc:
logger.debug("Rich diff render failed, using ANSI fallback: %s", exc)

# Original ANSI fallback — unchanged from upstream
rendered: list[str] = []
from_file = None
to_file = None
Expand Down Expand Up @@ -443,6 +478,24 @@ def _render_inline_unified_diff(diff: str) -> list[str]:
return rendered


def highlight_code(
code: str,
language: str | None = None,
filename: str | None = None,
) -> str:
"""Return an ANSI-highlighted version of *code* for terminal display.

When rich_output is unavailable the original string is returned unchanged.
"""
if not _RICH_OUTPUT:
return code
try:
return _rich_syntax.to_ansi(code, language=language, filename=filename)
except Exception as exc:
logger.debug("highlight_code failed: %s", exc)
return code


def _split_unified_diff_sections(diff: str) -> list[str]:
"""Split a unified diff into per-file sections."""
sections: list[list[str]] = []
Expand Down Expand Up @@ -531,6 +584,193 @@ def render_edit_diff_with_delta(
return _emit_inline_diff("\n".join(rendered_lines), print_fn)


# =========================================================================
# execute_code / read_file / terminal syntax highlight previews
# =========================================================================

_HIGHLIGHT_MAX_LINES = 40


def _emit_highlighted_lines(lines: list[str], print_fn) -> None:
"""Print *lines*, truncating at _HIGHLIGHT_MAX_LINES with a dim footer."""
_print = print_fn or print
if len(lines) <= _HIGHLIGHT_MAX_LINES:
for line in lines:
_print(line)
else:
for line in lines[:_HIGHLIGHT_MAX_LINES]:
_print(line)
omitted = len(lines) - _HIGHLIGHT_MAX_LINES
_print(f"\033[2m ╌╌ {omitted} more line{'s' if omitted != 1 else ''} omitted ╌╌\033[0m")


def _highlight_block(header: str, content: str, language: str, print_fn) -> bool:
"""Print a labelled syntax-highlighted block aligned with the ┊ tool log.

Format::

\033[2m ┊ <header>\033[0m
<highlighted content lines>
"""
_print = print_fn or print
_print(f"\033[2m ┊ {header}\033[0m")
if not _RICH_OUTPUT:
_emit_highlighted_lines(content.rstrip("\n").splitlines(), _print)
return True
try:
highlighted = _rich_syntax.to_ansi(content, language=language).rstrip("\n")
_emit_highlighted_lines(highlighted.splitlines(), _print)
return True
except Exception as exc:
logger.debug("highlight_block failed for %s: %s", header, exc)
return False


def render_execute_code_preview(code: str, print_fn=None) -> bool:
"""Print *code* with Python syntax highlighting.

The cute_msg line already labels the tool; this function prints only the
highlighted code (no header) so the output stays compact.
Returns True if anything was printed.
"""
if not code or not code.strip():
return False
_print = print_fn or print
if not _RICH_OUTPUT:
_emit_highlighted_lines(code.rstrip("\n").splitlines(), _print)
return True
try:
highlighted = _rich_syntax.to_ansi(code, language="python").rstrip("\n")
_emit_highlighted_lines(highlighted.splitlines(), _print)
return True
except Exception as exc:
logger.debug("execute_code highlight failed: %s", exc)
return False


def render_read_file_preview(path: str, result_json: str, print_fn=None) -> bool:
"""Print the content of a read_file result with syntax highlighting.

Language is detected from *path*'s extension. Returns False (no output)
when the file type is unknown — we don't highlight plain text or binary.
"""
if not path or not result_json:
return False
try:
import json as _json
result = _json.loads(result_json)
content = result.get("content", "")
except Exception:
return False
if not content or not content.strip():
return False

from pathlib import Path as _Path
if _RICH_OUTPUT:
from agent.rich_output import LanguageDetector as _LD
lang = _LD().detect_from_filename(_Path(path).name)
else:
lang = None
if not lang:
return False # unknown type — skip, don't guess

header = f"📄 {_Path(path).name}"
return _highlight_block(header, content, lang, print_fn)


_FILE_READ_COMMANDS = frozenset({
"cat", "head", "tail", "less", "more", "bat",
"sed", "awk", "grep", "cut", "sort", "uniq",
"nl", "od", "xxd", "hexdump",
})

# Commands that *execute* a file rather than reading it — the terminal output
# will be runtime stdout, not source code. Never highlight for these.
_FILE_EXEC_COMMANDS = frozenset({
"python", "python3", "python2",
"node", "nodejs", "deno", "bun",
"ruby", "perl", "php", "lua",
"bash", "sh", "zsh", "fish", "dash",
"Rscript", "julia",
})


def _extract_file_language_from_command(command: str):
"""Return (filename, language) if *command* is clearly reading a known source file.

Only fires when the leading verb is a known file-reader (cat, head, sed …).
Commands that *execute* files (node, python, bash …) are explicitly excluded
— their stdout is runtime output, not source code.

Parses tokens in reverse (file arg is typically last) and returns the first
token whose extension maps to a known language. Returns (None, None) if no
match — we never fall back to content-based detection for shell output.
"""
if not command:
return None, None
try:
import shlex as _shlex
tokens = _shlex.split(command)
except ValueError:
tokens = command.split()

if not tokens:
return None, None

# Check the leading verb (strip path prefix, e.g. /usr/bin/cat → cat)
from pathlib import Path as _Path
verb = _Path(tokens[0]).name
if verb in _FILE_EXEC_COMMANDS:
return None, None
if _FILE_READ_COMMANDS and verb not in _FILE_READ_COMMANDS:
# Not in the explicit read list — only proceed if it's clearly not an
# executor. Unknown commands might be aliases like 'bat' equivalents;
# allow them through so we don't over-block.
pass

if _RICH_OUTPUT:
from agent.rich_output import LanguageDetector as _LD
detector = _LD()
else:
return None, None

for tok in reversed(tokens):
if tok.startswith("-"):
continue
# Only consider tokens that look like a file path (contain a dot or slash)
if "." not in _Path(tok).name:
continue
lang = detector.detect_from_filename(_Path(tok).name)
if lang:
return _Path(tok).name, lang
return None, None


def render_terminal_preview(command: str, result_json: str, print_fn=None) -> bool:
"""Print terminal output with syntax highlighting when the command reads a source file.

Highlighting is only applied when a known-extension filename can be extracted
from *command* (e.g. ``cat foo.py``, ``sed -n '1,50p' app.ts``).
Returns False without printing anything if the language cannot be determined.
"""
if not command or not result_json:
return False
filename, lang = _extract_file_language_from_command(command)
if not lang:
return False
try:
import json as _json
result = _json.loads(result_json)
output = result.get("output", "")
except Exception:
return False
if not output or not output.strip():
return False

header = f"💻 {filename}"
return _highlight_block(header, output, lang, print_fn)


# =========================================================================
# KawaiiSpinner
# =========================================================================
Expand Down Expand Up @@ -950,6 +1190,8 @@ def _wrap(line: str) -> str:
}
return _wrap(f"┊ 🧪 rl {rl.get(tool_name, tool_name.replace('rl_', ''))} {dur}")
if tool_name == "execute_code":
if _code_highlight_active:
return _wrap(f"┊ 🐍 exec {dur}")
code = args.get("code", "")
first_line = code.strip().split("\n")[0] if code.strip() else ""
return _wrap(f"┊ 🐍 exec {_trunc(first_line, 35)} {dur}")
Expand Down
5 changes: 3 additions & 2 deletions agent/prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,9 @@ def _strip_yaml_frontmatter(content: str) -> str:
"destination — put the primary content directly in your response."
),
"cli": (
"You are a CLI AI Agent. Try not to use markdown but simple text "
"renderable inside a terminal."
"You are a CLI AI Agent. Your terminal supports full markdown rendering. "
"Use markdown freely for headings, bold, italic, code blocks, tables, "
"lists, blockquotes, and links to make responses clear and well-structured."
),
"sms": (
"You are communicating via SMS. Keep responses concise and use plain text "
Expand Down
Loading