feat(code): show (debug enabled) on splash when DEEPAGENTS_CODE_DEBUG is set (#4584)

When `DEEPAGENTS_CODE_DEBUG` has a truthy value, the startup splash
displays `(debug enabled)` so users can tell at a glance that debug
logging is active. The tag appears before `(local)` when both apply.

The debug tag remains visible when `DEEPAGENTS_CODE_HIDE_SPLASH_VERSION`
hides the version. Existing privacy behavior is preserved: the version,
`(local)` tag, and editable-install source path all remain hidden.

Made by [Open
SWE](https://openswe.vercel.app/agents/23f30b1b-d342-0b55-0549-193682eb1096)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Mason Daugherty
2026-07-13 01:42:08 -04:00
committed by GitHub
parent d44267358e
commit f10b877e5e
2 changed files with 242 additions and 24 deletions
@@ -98,6 +98,23 @@ def _local_tag_style(*, ansi: bool, colors: theme.ThemeColors) -> str | TStyle:
return TStyle(foreground=TColor.parse(colors.tool), bold=True)
def _debug_tag_style(*, ansi: bool, colors: theme.ThemeColors) -> str | TStyle:
"""Build the style for the `(debug enabled)` tag.
Args:
ansi: Whether the active theme is an ANSI terminal theme.
colors: Active Deep Agents theme colors.
Returns:
A bold yellow markup style under ANSI themes (whose palette the terminal
owns, so a parsed color could be invisible) or a bold themed warning
color otherwise.
"""
if ansi:
return "bold yellow"
return TStyle(foreground=TColor.parse(colors.warning), bold=True)
def _home_prefixed(cwd: str) -> str:
"""Format a directory path, using `~` for the home directory when possible.
@@ -125,12 +142,16 @@ def _home_prefixed(cwd: str) -> str:
class WelcomeBanner(Static):
"""Compact welcome banner shown at startup.
Renders a bordered box with the product title and version, followed by rows
that appear only when their data (and any env gate) is present. In render
order: the active model (`SPLASH_SHOW_MODEL`, opt-in), working directory
(`SPLASH_SHOW_CWD`, opt-in), LangSmith tracing project and its replica (each
clickable once its URL resolves), thread ID (debug mode only), and the MCP
tool count. MCP server warnings and the editable-install path follow.
Renders a bordered box with the product title and optional version. A
`(debug enabled)` tag appears when `DEEPAGENTS_CODE_DEBUG` is enabled
(truthy), even when the version is hidden. A `(local)` tag appears for
editable installs only when the version is shown. Rows follow that appear
only when their data (and any env gate) is present. In render order: the
active model
(`SPLASH_SHOW_MODEL`, opt-in), working directory (`SPLASH_SHOW_CWD`,
opt-in), LangSmith tracing project and its replica (each clickable once its
URL resolves), thread ID (debug mode only), and the MCP tool count. MCP
server warnings and the editable-install path follow.
"""
# Disable Textual's auto_links to prevent a flicker cycle: Style.__add__
@@ -208,7 +229,8 @@ class WelcomeBanner(Static):
else None
)
self._project_urls: dict[str, str] = {}
self._show_thread_id = is_env_truthy(DEBUG)
self._debug_enabled = is_env_truthy(DEBUG)
self._show_thread_id = self._debug_enabled
super().__init__(self._build_banner(), **kwargs)
def on_mount(self) -> None:
@@ -328,7 +350,9 @@ class WelcomeBanner(Static):
"""Build the banner content.
Returns:
Content with the title and version, followed by any applicable rows
Content with the title, optional version, and any applicable header
tags (`(debug enabled)` when debug is on; `(local)` for editable
installs when the version is shown), followed by any applicable rows
in order: model (when `SPLASH_SHOW_MODEL`), directory (when
`SPLASH_SHOW_CWD`), tracing and replica (each clickable once its URL
resolves), thread ID (debug only), MCP tool count, MCP server
@@ -350,8 +374,15 @@ class WelcomeBanner(Static):
]
if not self._hide_version:
parts.append((f" v{__version__}", "dim"))
if _is_editable_install():
parts.append((" (local)", _local_tag_style(ansi=ansi, colors=colors)))
if self._debug_enabled:
parts.append(
(
" (debug enabled)",
_debug_tag_style(ansi=ansi, colors=colors),
)
)
if not self._hide_version and _is_editable_install():
parts.append((" (local)", _local_tag_style(ansi=ansi, colors=colors)))
# Row labels share a common column width so values stay aligned; the
# longest label ("directory:") needs 11 columns including its space.
@@ -5,6 +5,7 @@ from unittest.mock import MagicMock, patch
import pytest
from textual.content import Content
from textual.style import Style as TStyle
from deepagents_code._env_vars import (
DEBUG,
@@ -18,6 +19,7 @@ from deepagents_code._env_vars import (
from deepagents_code._version import __version__
from deepagents_code.tui.widgets.welcome import (
WelcomeBanner,
_debug_tag_style,
_home_prefixed,
_langsmith_project_link,
_langsmith_project_link_style,
@@ -29,6 +31,50 @@ _EDITABLE_PATH = "deepagents_code.tui.widgets.welcome._get_editable_install_path
_PROJECT_NAME = "deepagents_code.tui.widgets.welcome.get_langsmith_project_name"
_REPLICA_PROJECT = "deepagents_code.tui.widgets.welcome.get_langsmith_replica_project"
_FETCH_URL = "deepagents_code.tui.widgets.welcome.fetch_langsmith_project_url"
_DEBUG_STYLE = "deepagents_code.tui.widgets.welcome._debug_tag_style"
_LOCAL_STYLE = "deepagents_code.tui.widgets.welcome._local_tag_style"
def _raw_style_covering(content: Content, needle: str) -> str | TStyle:
"""Return the style of the single span whose text contains `needle`.
The style may be a `TStyle` (built from a themed style) or a raw `str` style
string (used under the ANSI theme branch, which `Content.assemble` preserves
verbatim — see `test_debug_tag_uses_ansi_markup_under_ansi_theme`). Use
`_style_covering` when the style must be a `TStyle`. Indexes the public
`content.plain` and reads the public `Span.style` field; update if Textual's
span model changes.
Args:
content: Assembled banner content to inspect.
needle: Substring identifying the span whose style to return.
Returns:
The style of the one span covering `needle`.
"""
spans = [s for s in content.spans if needle in content.plain[s.start : s.end]]
assert len(spans) == 1, f"expected exactly one span covering {needle!r}"
return spans[0].style
def _style_covering(content: Content, needle: str) -> TStyle:
"""Return the `TStyle` of the single span whose text contains `needle`.
Only usable for spans whose style resolves to a `TStyle` (i.e. built from a
themed `TStyle`, not a raw `str` style string as used under the ANSI theme
branch, which `Content.assemble` preserves as a raw `str` — see
`test_debug_tag_uses_ansi_markup_under_ansi_theme`).
Args:
content: Assembled banner content to inspect.
needle: Substring identifying the span whose style to return.
Returns:
The `TStyle` of the one span covering `needle`.
"""
style = _raw_style_covering(content, needle)
assert isinstance(style, TStyle)
return style
def _make_banner(
@@ -175,7 +221,6 @@ class TestLocalTagStyle:
def test_non_ansi_uses_themed_color(self) -> None:
"""Non-ANSI themes color the tag with the theme's tool color."""
from textual.color import Color as TColor
from textual.style import Style as TStyle
from deepagents_code.theme import DARK_COLORS
@@ -185,6 +230,27 @@ class TestLocalTagStyle:
assert style.foreground == TColor.parse(DARK_COLORS.tool)
class TestDebugTagStyle:
"""Tests for the `(debug enabled)` tag style."""
def test_ansi_uses_bold_yellow_markup(self) -> None:
"""Under ANSI themes the tag stays visible via bold yellow terminal text."""
from deepagents_code.theme import DARK_COLORS
assert _debug_tag_style(ansi=True, colors=DARK_COLORS) == "bold yellow"
def test_non_ansi_uses_themed_warning_color(self) -> None:
"""Non-ANSI themes color the tag with the theme's warning color."""
from textual.color import Color as TColor
from deepagents_code.theme import DARK_COLORS
style = _debug_tag_style(ansi=False, colors=DARK_COLORS)
assert isinstance(style, TStyle)
assert style.bold is True
assert style.foreground == TColor.parse(DARK_COLORS.warning)
class TestTitle:
"""Tests for the banner title line."""
@@ -199,19 +265,145 @@ class TestTitle:
assert f"v{__version__}" in plain
assert "(local)" not in plain
def test_hides_version_when_env_set(self) -> None:
"""`HIDE_SPLASH_VERSION` removes the version from the title."""
plain = _make_banner(env={HIDE_SPLASH_VERSION: "1"})._build_banner().plain
def test_hides_version_and_local_tag_when_env_set(self) -> None:
"""`HIDE_SPLASH_VERSION` removes version and local-install details."""
with patch(_EDITABLE, return_value=True):
plain = _make_banner(env={HIDE_SPLASH_VERSION: "1"})._build_banner().plain
assert f"v{__version__}" not in plain
assert "(local)" not in plain
def test_marks_editable_install_as_local(self) -> None:
"""Editable installs append a `(local)` tag to the version."""
"""Editable installs show a `(local)` tag."""
with patch(_EDITABLE, return_value=True):
plain = _make_banner()._build_banner().plain
assert f"v{__version__}" in plain
assert "(local)" in plain
def test_no_debug_tag_by_default(self) -> None:
"""No `(debug enabled)` tag when `DEEPAGENTS_CODE_DEBUG` is unset."""
with patch(_EDITABLE, return_value=False):
plain = _make_banner()._build_banner().plain
assert "(debug enabled)" not in plain
def test_no_debug_tag_when_env_falsy(self) -> None:
"""A present-but-falsy `DEEPAGENTS_CODE_DEBUG` shows no `(debug enabled)` tag.
Locks the truthy gate (`is_env_truthy`) against a regression to a bare
presence check (`DEBUG in os.environ`), which every other test would pass.
"""
with patch(_EDITABLE, return_value=False):
plain = _make_banner(env={DEBUG: "0"})._build_banner().plain
assert "(debug enabled)" not in plain
def test_marks_debug_enabled_when_env_set(self) -> None:
"""`DEEPAGENTS_CODE_DEBUG` shows a `(debug enabled)` tag."""
with patch(_EDITABLE, return_value=False):
plain = _make_banner(env={DEBUG: "1"})._build_banner().plain
assert f"v{__version__}" in plain
# Leading space guards the separator from the preceding segment.
assert " (debug enabled)" in plain
assert "(local)" not in plain
assert plain.index(f"v{__version__}") < plain.index("(debug enabled)")
def test_debug_tag_precedes_local_tag(self) -> None:
"""`(debug enabled)` renders before `(local)` when both apply."""
with patch(_EDITABLE, return_value=True):
plain = _make_banner(env={DEBUG: "1"})._build_banner().plain
assert "(debug enabled)" in plain
assert "(local)" in plain
assert plain.index("(debug enabled)") < plain.index("(local)")
def test_version_debug_local_render_in_order(self) -> None:
"""Version, `(debug enabled)`, and `(local)` render in that fixed order.
The pairwise ordering assertions each pin only two of the three segments,
and under different editable states; this locks all three in one banner.
"""
with patch(_EDITABLE, return_value=True):
plain = _make_banner(env={DEBUG: "1"})._build_banner().plain
assert (
plain.index(f"v{__version__}")
< plain.index("(debug enabled)")
< plain.index("(local)")
)
def test_debug_tag_when_version_hidden(self) -> None:
"""The debug tag remains visible without exposing local-install details."""
with patch(_EDITABLE, return_value=True):
plain = (
_make_banner(env={DEBUG: "1", HIDE_SPLASH_VERSION: "1"})
._build_banner()
.plain
)
assert f"v{__version__}" not in plain
assert "(debug enabled)" in plain
assert "(local)" not in plain
def test_title_tags_carry_their_own_styles(self) -> None:
"""Each title tag's span carries its own style helper's output.
Guards against wiring regressions where a tag renders with the wrong
helper's style or the style lands on the wrong segment; the plain-text
assertions above cannot catch either.
"""
from textual.color import Color as TColor
debug_style = TStyle(foreground=TColor.parse("#010203"), bold=True)
local_style = TStyle(foreground=TColor.parse("#040506"), bold=True)
with (
patch(_EDITABLE, return_value=True),
patch(_DEBUG_STYLE, return_value=debug_style),
patch(_LOCAL_STYLE, return_value=local_style),
):
content = _make_banner(env={DEBUG: "1"})._build_banner()
assert _style_covering(content, "(debug enabled)").foreground == TColor.parse(
"#010203"
)
assert _style_covering(content, "(local)").foreground == TColor.parse("#040506")
def test_debug_tag_uses_ansi_markup_under_ansi_theme(self) -> None:
"""Under an ANSI theme the assembled debug span carries bold-yellow markup.
`TestDebugTagStyle` covers `_debug_tag_style` in isolation; only an
assembled banner confirms the ANSI branch's markup survives
`Content.assemble`, which keeps it a raw `str` rather than a parsed
`TStyle` (so `_style_covering` cannot be used here). Guards the ANSI
rendering path that every other assembled test misses, since they run
under the non-ANSI default theme.
"""
from textual._context import active_app
app = active_app.get()
previous_theme = app.theme
app.theme = "ansi-dark"
try:
with patch(_EDITABLE, return_value=False):
content = _make_banner(env={DEBUG: "1"})._build_banner()
finally:
app.theme = previous_theme
assert _raw_style_covering(content, "(debug enabled)") == "bold yellow"
def test_debug_tag_uses_themed_warning_color_when_assembled(self) -> None:
"""The real themed warning color reaches the assembled debug span.
`TestDebugTagStyle` checks `_debug_tag_style` in isolation and
`test_title_tags_carry_their_own_styles` patches it with a sentinel;
neither confirms the unpatched helper's themed color lands on the span
under the default (non-ANSI) theme. Anchored to the banner's resolved
theme colors so it tracks whatever palette the active theme provides.
"""
from textual.color import Color as TColor
from deepagents_code.theme import get_theme_colors
with patch(_EDITABLE, return_value=False):
banner = _make_banner(env={DEBUG: "1"})
content = banner._build_banner()
warning = get_theme_colors(banner).warning
assert _style_covering(content, "(debug enabled)").foreground == TColor.parse(
warning
)
class TestModelLine:
"""Tests for the model row."""
@@ -317,8 +509,6 @@ class TestTracingLine:
def test_project_name_is_clickable_when_url_resolved(self) -> None:
"""The project name is a hyperlink when the URL has been fetched."""
from textual.style import Style as TStyle
widget = _make_banner(
project_name="dcode-johannes",
project_urls={
@@ -330,13 +520,11 @@ class TestTracingLine:
s for s in content.spans if isinstance(s.style, TStyle) and s.style.link
]
assert any(
"dcode-johannes" in content._text[s.start : s.end] for s in linked_spans
"dcode-johannes" in content.plain[s.start : s.end] for s in linked_spans
)
def test_project_name_not_clickable_without_url(self) -> None:
"""The project name has no link when the URL has not been fetched."""
from textual.style import Style as TStyle
widget = _make_banner(project_name="dcode-johannes")
content = widget._build_banner()
linked = [
@@ -440,8 +628,6 @@ class TestReplicaTracingLine:
def test_replica_project_is_clickable_when_url_resolved(self) -> None:
"""The replica project is a hyperlink when the URL has been fetched."""
from textual.style import Style as TStyle
widget = _make_banner(
project_name="dcode-primary",
replica_project="dcode-replica",
@@ -454,7 +640,7 @@ class TestReplicaTracingLine:
s for s in content.spans if isinstance(s.style, TStyle) and s.style.link
]
assert any(
"dcode-replica" in content._text[s.start : s.end] for s in linked_spans
"dcode-replica" in content.plain[s.start : s.end] for s in linked_spans
)
async def test_fetch_and_update_sets_primary_and_replica_urls(self) -> None:
@@ -596,13 +782,14 @@ class TestEditableInstallPath:
assert "installed:" not in plain
def test_no_install_path_when_version_hidden(self) -> None:
"""No install path row when the version is hidden."""
"""Hiding the version also hides the editable-install path."""
with (
patch(_EDITABLE, return_value=True),
patch(_EDITABLE_PATH, return_value="~/code"),
):
plain = _make_banner(env={HIDE_SPLASH_VERSION: "1"})._build_banner().plain
assert "installed:" not in plain
assert "~/code" not in plain
def test_no_install_path_when_cwd_hidden(self) -> None:
"""No install path row when local path displays are hidden."""