From d43a5e47a1cddd8b8c6766d5a5b91af47db8746a Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 26 Dec 2025 23:35:08 +0100 Subject: [PATCH] Update CHANGELOG with CustomMessageEntry and hook rendering API --- packages/coding-agent/CHANGELOG.md | 8 ++- .../interactive/components/custom-message.ts | 58 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 packages/coding-agent/src/modes/interactive/components/custom-message.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a77e38da..0c7fb9c8 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -27,8 +27,13 @@ - `before_switch` event now has `targetSessionFile`, `switch` event has `previousSessionFile` - Removed `resolveApiKey` (use `modelRegistry.getApiKey(model)`) - Hooks can return `compaction.details` to store custom data (e.g., ArtifactIndex for structured compaction) +- **Hook API**: + - New `pi.renderCustomMessage(customType, renderer)` to register custom renderers for `CustomMessageEntry` + - `CustomMessageRenderer` type: `(entry, options, theme) => Component | null` + - Renderers return inner content; the TUI wraps it in a styled Box - **SessionManager**: - `getSessionFile()` now returns `string | undefined` (undefined for in-memory sessions) +- **Themes**: Custom themes must add `customMessageBg`, `customMessageText`, `customMessageLabel` color tokens ### Added @@ -38,8 +43,9 @@ - **Entry IDs**: Session entries now use short 8-character hex IDs instead of full UUIDs - **API key priority**: `ANTHROPIC_OAUTH_TOKEN` now takes precedence over `ANTHROPIC_API_KEY` -- **New entry types**: `BranchSummaryEntry` for branch context, `CustomEntry` for hook state persistence, `LabelEntry` for user-defined bookmarks +- **New entry types**: `BranchSummaryEntry` for branch context, `CustomEntry` for hook state persistence, `CustomMessageEntry` for hook-injected context messages, `LabelEntry` for user-defined bookmarks - **Entry labels**: New `getLabel(id)` and `appendLabelChange(targetId, label)` methods for labeling entries. Labels are included in `SessionTreeNode` for UI/export. +- **TUI**: `CustomMessageEntry` renders with purple styling (customMessageBg, customMessageText, customMessageLabel theme colors). Entries with `display: false` are hidden. ### Fixed diff --git a/packages/coding-agent/src/modes/interactive/components/custom-message.ts b/packages/coding-agent/src/modes/interactive/components/custom-message.ts new file mode 100644 index 00000000..5e952c32 --- /dev/null +++ b/packages/coding-agent/src/modes/interactive/components/custom-message.ts @@ -0,0 +1,58 @@ +import type { TextContent } from "@mariozechner/pi-ai"; +import { Box, Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui"; +import type { CustomMessageRenderer } from "../../../core/hooks/types.js"; +import type { CustomMessageEntry } from "../../../core/session-manager.js"; +import { getMarkdownTheme, theme } from "../theme/theme.js"; + +/** + * Component that renders a custom message entry from hooks. + * Uses distinct styling to differentiate from user messages. + */ +export class CustomMessageComponent extends Container { + constructor(entry: CustomMessageEntry, customRenderer?: CustomMessageRenderer) { + super(); + + this.addChild(new Spacer(1)); + + // Create box with purple background + const box = new Box(1, 1, (t) => theme.bg("customMessageBg", t)); + + // Try custom renderer first + if (customRenderer) { + try { + const component = customRenderer(entry, { expanded: false }, theme); + if (component) { + box.addChild(component); + this.addChild(box); + return; + } + } catch { + // Fall through to default rendering + } + } + + // Default rendering: label + content + const label = theme.fg("customMessageLabel", `\x1b[1m[${entry.customType}]\x1b[22m`); + box.addChild(new Text(label, 0, 0)); + box.addChild(new Spacer(1)); + + // Extract text content + let text: string; + if (typeof entry.content === "string") { + text = entry.content; + } else { + text = entry.content + .filter((c): c is TextContent => c.type === "text") + .map((c) => c.text) + .join("\n"); + } + + box.addChild( + new Markdown(text, 0, 0, getMarkdownTheme(), { + color: (text: string) => theme.fg("customMessageText", text), + }), + ); + + this.addChild(box); + } +}