Commit 3029212752c5

Vincent Demeester <vincent@sbr.pm>
2026-02-06 10:56:16
feat(ai-storage): display session list as markdown messages
Changed /list-sessions command from TUI widget to conversation message using pi.sendMessage() with custom renderer. Sessions now display as markdown with a distinct background (toolSuccessBg). Added registerMessageRenderer for ai-storage-sessions custom type using Box with Markdown component for proper rendering.
1 parent 6e01b57
Changed files (1)
dots
pi
agent
extensions
ai-storage
dots/pi/agent/extensions/ai-storage/index.ts
@@ -21,6 +21,8 @@
  */
 
 import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
+import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
+import { Box, Markdown } from "@mariozechner/pi-tui";
 import { writeFile, mkdir, appendFile, readFile, readdir, unlink } from "node:fs/promises";
 import { existsSync, openSync } from "node:fs";
 import { join, dirname } from "node:path";
@@ -37,6 +39,15 @@ export default function (pi: ExtensionAPI) {
 	const LEARNINGS_DIR = join(AI_DATA_DIR, "learnings");
 	const PENDING_DIR = join(AI_DATA_DIR, ".pending");
 
+	// Register custom message renderer for session listings
+	pi.registerMessageRenderer("ai-storage-sessions", (message, { expanded }, theme) => {
+		const mdTheme = getMarkdownTheme();
+		// Use toolSuccessBg for a distinct background (blueish/greenish depending on theme)
+		const box = new Box(1, 1, (t) => theme.bg("toolSuccessBg", t));
+		box.addChild(new Markdown(message.content, 0, 0, mdTheme));
+		return box;
+	});
+
 	// Path to the background summarizer script (sibling to this file)
 	const SUMMARIZER_SCRIPT = join(dirname(import.meta.url.replace("file://", "")), "summarizer.ts");
 
@@ -752,7 +763,6 @@ After generating the summary, use the save_session_to_history tool to save it${c
 	pi.registerCommand("list-sessions", {
 		description: "List sessions. Usage: /list-sessions [today|yesterday|last week|last N days|YYYY-MM-DD|YYYY-MM-DD..YYYY-MM-DD|search <query>]",
 		handler: async (args, ctx) => {
-			const theme = ctx.ui.theme;
 			const argStr = (args || "").trim();
 
 			// Check for search mode
@@ -778,23 +788,31 @@ After generating the summary, use the save_session_to_history tool to save it${c
 
 					const files = rgResult.split("\n").filter(Boolean).slice(0, 20); // Limit to 20 results
 
-					const header = theme.bold(`๐Ÿ” Sessions matching "${query}"`);
-					const separator = theme.fg("dim", "โ”€".repeat(50));
+					// Build markdown content
+					const lines: string[] = [];
+					lines.push(`## ๐Ÿ” Sessions matching "${query}"`);
+					lines.push("");
+					lines.push(`*${files.length} result(s)*`);
+					lines.push("");
 
-					const fileLines = files.map((filepath) => {
+					for (const filepath of files) {
 						const filename = filepath.split("/").pop() || "";
 						const date = filename.slice(0, 10);
 						const desc = filename.slice(11).replace(".md", "").replace(/-/g, " ");
-						return `  ${theme.fg("accent", "โ€ข")} ${theme.fg("dim", date)} ${desc}\n    ${theme.fg("dim", filepath)}`;
-					});
-
-					const widgetLines = [header, separator, ...fileLines];
-					if (files.length === 20) {
-						widgetLines.push(theme.fg("warning", "  (showing first 20 results)"));
+						lines.push(`- **${date}** ${desc}`);
+						lines.push(`  \`${filepath}\``);
 					}
-					ctx.ui.setWidget("list-sessions", widgetLines);
 
-					setTimeout(() => ctx.ui.setWidget("list-sessions", undefined), 20000);
+					if (files.length === 20) {
+						lines.push("");
+						lines.push("*(showing first 20 results)*");
+					}
+
+					pi.sendMessage({
+						customType: "ai-storage-sessions",
+						content: lines.join("\n"),
+						display: true,
+					});
 				} catch (error) {
 					ctx.ui.notify(`Search error: ${error}`, "error");
 				}
@@ -846,21 +864,30 @@ After generating the summary, use the save_session_to_history tool to save it${c
 					return;
 				}
 
-				const header = theme.bold(`๐Ÿ“‹ Sessions - ${label} (${allFiles.length})`);
-				const separator = theme.fg("dim", "โ”€".repeat(50));
+				// Build markdown content
+				const lines: string[] = [];
+				lines.push(`## ๐Ÿ“‹ Sessions - ${label}`);
+				lines.push("");
+				lines.push(`*${allFiles.length} session(s)*`);
+				lines.push("");
 
-				const fileLines = allFiles.slice(0, 30).map(({ date, file, path }) => {
+				const displayFiles = allFiles.slice(0, 30);
+				for (const { date, file, path } of displayFiles) {
 					const desc = file.slice(11).replace(".md", "").replace(/-/g, " ");
-					return `  ${theme.fg("accent", "โ€ข")} ${theme.fg("dim", date)} ${desc}\n    ${theme.fg("dim", path)}`;
-				});
-
-				const widgetLines = [header, separator, ...fileLines];
-				if (allFiles.length > 30) {
-					widgetLines.push(theme.fg("warning", `  (showing 30 of ${allFiles.length})`));
+					lines.push(`- **${date}** ${desc}`);
+					lines.push(`  \`${path}\``);
 				}
-				ctx.ui.setWidget("list-sessions", widgetLines);
 
-				setTimeout(() => ctx.ui.setWidget("list-sessions", undefined), 20000);
+				if (allFiles.length > 30) {
+					lines.push("");
+					lines.push(`*(showing 30 of ${allFiles.length})*`);
+				}
+
+				pi.sendMessage({
+					customType: "ai-storage-sessions",
+					content: lines.join("\n"),
+					display: true,
+				});
 			} catch (error) {
 				ctx.ui.notify(`Error listing sessions: ${error}`, "error");
 			}