fix(dashboard): advertise truecolor to the embedded chat TUI (#60576)

Headless/hosted deploys run the dashboard server without COLORTERM in
the process environment, so chalk inside the PTY-spawned TUI child
downgraded every skin hex color to the xterm 256 palette — the default
skin's bronze banner border (#CD7F32) snapped to palette 173 (#D7875F,
salmon red) and the gold caduceus rendered red/yellow on fresh cloud
instances. Local launches never reproduced it because the operator's
interactive terminal leaks COLORTERM=truecolor into the server env.

xterm.js always renders 24-bit RGB, so the dashboard PTY child should
always advertise truecolor: backfill COLORTERM=truecolor in
_resolve_chat_argv via setdefault (an explicit operator value wins).

Verified with a clean-env PTY probe of the real TUI binary:
no COLORTERM -> 0 truecolor SGRs / 165 palette-256 (salmon 38;5;173);
with the backfill -> 166 truecolor SGRs, exact bronze 38;2;205;127;50.
This commit is contained in:
Teknium 2026-07-07 17:17:51 -07:00 committed by GitHub
parent bf7639138e
commit 5633fa19b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 44 additions and 0 deletions

View file

@ -13087,6 +13087,17 @@ def _resolve_chat_argv(
# the dashboard PTY path.
env.setdefault("HERMES_TUI_DISABLE_MOUSE", "1")
env.setdefault("HERMES_TUI_INLINE", "1")
# The dashboard terminal is xterm.js, which always renders 24-bit RGB.
# But chalk inside the TUI child decides its color depth from the
# SERVER process env — and hosted/cloud deploys run the dashboard under
# a process manager (container init, systemd) with no COLORTERM, so
# chalk downgrades every hex color to the xterm 256 palette. The skin's
# bronze border #CD7F32 snaps to palette 173 (#D7875F, salmon-red) and
# the banner reads red/yellow instead of gold. Local launches dodge
# this only because the operator's interactive terminal leaks
# COLORTERM=truecolor into os.environ. Backfill it for the PTY child;
# setdefault so an explicit operator value still wins.
env.setdefault("COLORTERM", "truecolor")
env["HERMES_TUI_DASHBOARD"] = "1"
if profile_dir is not None:

View file

@ -5919,6 +5919,39 @@ class TestPtyWebSocket:
assert env["HERMES_TUI_INLINE"] == "1"
assert env["HERMES_TUI_DISABLE_MOUSE"] == "1"
def test_resolve_chat_argv_backfills_colorterm_truecolor(self, monkeypatch):
"""Headless servers (cloud/systemd) have no COLORTERM, which made
chalk in the TUI child degrade skin hex colors to the xterm 256
palette (gold banner rendered salmon-red). xterm.js always supports
24-bit color, so the PTY env must advertise truecolor."""
import hermes_cli.main as main_mod
monkeypatch.setattr(
main_mod,
"_make_tui_argv",
lambda project_root, tui_dev=False: (["node", "dist/entry.js"], "/tmp/ui-tui"),
)
monkeypatch.delenv("COLORTERM", raising=False)
_argv, _cwd, env = self.ws_module._resolve_chat_argv()
assert env["COLORTERM"] == "truecolor"
def test_resolve_chat_argv_keeps_operator_colorterm(self, monkeypatch):
"""An explicit operator COLORTERM wins over the backfill."""
import hermes_cli.main as main_mod
monkeypatch.setattr(
main_mod,
"_make_tui_argv",
lambda project_root, tui_dev=False: (["node", "dist/entry.js"], "/tmp/ui-tui"),
)
monkeypatch.setenv("COLORTERM", "24bit")
_argv, _cwd, env = self.ws_module._resolve_chat_argv()
assert env["COLORTERM"] == "24bit"
def test_resolve_chat_argv_applies_terminal_backend_config(
self, monkeypatch, _isolate_hermes_home
):