feat(messaging): centralize banner formatting with opt-in timestamps#329
Open
mattnico wants to merge 1 commit intompfaffenberger:mainfrom
Open
feat(messaging): centralize banner formatting with opt-in timestamps#329mattnico wants to merge 1 commit intompfaffenberger:mainfrom
mattnico wants to merge 1 commit intompfaffenberger:mainfrom
Conversation
Adds code_puppy.messaging.banner as the single source of truth for how
banner tags are turned into Rich markup. The three existing banner-emitting
code paths now delegate to it:
* RichConsoleRenderer._format_banner (message-bus path)
* event_stream_handler._print_thinking_banner / _print_response_banner
(live-stream path)
* autosave_menu.display_resumed_history (session-resume path)
Previously each path constructed banner markup itself, so adding any new
behavior (timestamps, spacing, etc.) meant patching three places. The
resume-history path in particular hardcoded its own AGENT RESPONSE banner
that bypassed _format_banner entirely.
New opt-in config keys (all default to historical behavior, so existing
users see no change):
* banner_timestamps_enabled (bool, default False)
Append a dim [HH:MM:SS] annotation after every banner tag.
* banner_timestamp_format (str, default '%H:%M:%S')
strftime format for the annotation. Validated on set: any format
that produces identical output for two distinct datetimes is
rejected (catches typos like '%Q-bogus' that Python silently
passes through as literals).
* banner_newline_after_tag (bool, default False)
Append a newline after every banner tag so each banner sits alone
on its line and following content drops to the next line. Matches
the historical AGENT RESPONSE convention for every banner.
Resumed sessions now display each AGENT RESPONSE banner with the *original*
timestamp of the message (pulled from msg.timestamp, which pydantic-ai
already stores in UTC on every message). Aware datetimes are converted to
the user's local time before strftime, so reloaded sessions show 'when
this actually happened' rather than 'when I reloaded'.
Tests:
* tests/messaging/test_banner.py: 16 new tests covering the strftime
validator, timestamp suffix (disabled / now / naive / aware), and
format_banner (default behavior, timestamp, newline, both).
* tests/test_rich_renderer.py: re-target the patch site (mock now lives
on code_puppy.config.get_banner_color, since _format_banner delegates).
* tests/agents/test_event_stream_handler.py: re-target patches similarly
(13 occurrences).
* tests/test_config.py: register the three new keys in the expected
sorted-key list.
Owner
|
Would love to see some screenshots of the new setup |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR centralizes how Code Puppy renders banner tags (
AGENT RESPONSE,EDIT FILE,SHELL COMMAND,THINKING, etc.) into a single helper, and uses that consolidation to introduce three opt-in formatting options. All three options default to historical behavior, so this is a no-op for users who don't enable them.Motivation
Banner formatting was duplicated across three code paths:
RichConsoleRenderer._format_banner— message-bus renderingevent_stream_handler._print_thinking_banner/_print_response_banner— live-stream renderingautosave_menu.display_resumed_history— session-resume rendering (which constructed anAGENT RESPONSEbanner inline, bypassing_format_bannerentirely)That meant any new banner behavior had to be implemented in three places, and the resume-history path silently diverged from the other two. While experimenting with a banner-formatting plugin I kept hitting that fan-out — every monkey-patch had to know about all three sites. This PR fixes the underlying shape: one helper, one place to change.
What's in the PR
New module:
code_puppy/messaging/banner.pySingle source of truth for banner markup:
Builds the same Rich markup string the codebase has always produced, but allows opt-in additions controlled by config:
[HH:MM:SS]timestamp annotation on the same line, just outside the colored banner blockAGENT RESPONSEalready did)Three new config keys (all default-off)
banner_timestamps_enabledfalse[HH:MM:SS]after every banner tagbanner_timestamp_format%H:%M:%Sbanner_newline_after_tagfalse\nafter every banner tagset_banner_timestamp_format()validates inputs by checking that the format produces different output for two distinct datetimes — Python'sstrftimeis permissive and silently passes unknown directives through as literals (e.g.%Q-bogusbecomesQ-bogus), so this catches typos that would otherwise produce a banner displaying garbage forever.Refactored callers (now ~3-line delegations)
RichConsoleRenderer._format_banner→ callsformat_banner(banner_name, text)event_stream_handler._print_thinking_banner/_print_response_banner→ callformat_banner("thinking", "THINKING")/format_banner("agent_response", "AGENT RESPONSE")autosave_menu.display_resumed_history→ callsformat_banner("agent_response", "AGENT RESPONSE", when=msg.timestamp)Bonus fix: resumed sessions show original timestamps
pydantic-aialready storesmsg.timestamp(UTC) on every message. Whenbanner_timestamps_enabled=true, resumed-session banners now use that stored timestamp (converted to local time) instead ofdatetime.now(). So reloaded banners show when the message actually happened, not when you reloaded the session.This required no new metadata, no sidecar storage, no content hashing — the data was already there.
Defaults & backward compatibility
Every new option defaults to behavior that matches
mainexactly:Identical byte-for-byte to the previous markup. Existing users see zero change unless they explicitly opt in via
/set banner_timestamps_enabled=true(or the other keys).Tests
tests/messaging/test_banner.py— 16 tests covering the strftime validator, timestamp-suffix helper (disabled / live / naive datetime / aware datetime / fallback), andformat_banner(default behavior, timestamp-only, newline-only, both combined).tests/test_rich_renderer.py,tests/agents/test_event_stream_handler.py,tests/test_config.py— re-pointed mocks tocode_puppy.config.get_banner_color(since_format_bannernow delegates rather than calling its own_get_banner_colorhook), and added the three new keys to the expected sorted-key list.Full suite results on this branch: 9399 passed, 88 skipped, 1 xpassed, 4 failed. The 4 failures (
test_run_pending_credentials_success,test_run_prompt_with_attachments_uses_spinner,TestDefaultAgent::test_default,test_opus_46_reverse_name_also_works) all reproduce on a clean checkout oforigin/mainand are unrelated to this change.Out of scope
EDIT FILE/SHELL COMMANDbanners todisplay_resumed_history(it currently shows tool calls/returns as collapsed dim text). Easy follow-up but it's a separable UX decision./setcommand, which already discovers them viaget_config_keys().Try it
Then trigger any tool / response, or
/resumea previous session, to see the new formatting.