diff --git a/pi/files/agent/extensions/timestamps.ts b/pi/files/agent/extensions/timestamps.ts index 8b76fa4..2b15e1c 100644 --- a/pi/files/agent/extensions/timestamps.ts +++ b/pi/files/agent/extensions/timestamps.ts @@ -2,29 +2,36 @@ * Timestamps extension for Pi. * * - Shows elapsed session time in footer (updates every second) - * - Shows how long the last turn took (from your message to agent completion) + * - Shows how long the last turn took + * - Injects timestamp markers without triggering extra turns */ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { Box, Text } from "@mariozechner/pi-tui"; -// Track session time and turn durations +// Track session time let sessionStart = Date.now(); let timerHandle: ReturnType | null = null; let turnStartTime: number | null = null; let lastTurnDuration: number | null = null; +function formatTime(date: Date): string { + return date.toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }); +} + function formatElapsed(ms: number): string { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); - if (hours > 0) { - return `${hours}h ${minutes % 60}m`; - } else if (minutes > 0) { - return `${minutes}m ${seconds % 60}s`; - } else { - return `${seconds}s`; - } + if (hours > 0) return `${hours}h ${minutes % 60}m`; + if (minutes > 0) return `${minutes}m ${seconds % 60}s`; + return `${seconds}s`; } function formatDuration(ms: number): string { @@ -37,25 +44,47 @@ export default function (pi: ExtensionAPI) { const updateStatus = (ctx: { ui: { setStatus: (id: string, text: string | undefined) => void; theme: { fg: (color: string, text: string) => string } } }) => { const elapsed = Date.now() - sessionStart; let status = ctx.ui.theme.fg("dim", `⏱ ${formatElapsed(elapsed)}`); - - // Show last turn duration if available if (lastTurnDuration !== null) { status += ctx.ui.theme.fg("muted", ` | took ${formatDuration(lastTurnDuration)}`); } - ctx.ui.setStatus("timestamps", status); }; + // Renderer for user timestamp + pi.registerMessageRenderer("timestamp-prefix", (message, _options, theme) => { + const details = message.details as { timestamp: number; elapsed: string } | undefined; + if (!details) return new Text(""); + + const timeStr = formatTime(new Date(details.timestamp)); + const line = theme.fg("dim", `◆ ${timeStr} (+${details.elapsed})`); + const box = new Box(0, 0, undefined); + box.addChild(new Text(line, 0, 0)); + return box; + }); + + // Renderer for assistant timestamp + pi.registerMessageRenderer("timestamp-suffix", (message, _options, theme) => { + const details = message.details as { timestamp: number; elapsed: string; duration?: number } | undefined; + if (!details) return new Text(""); + + const timeStr = formatTime(new Date(details.timestamp)); + let line = `○ ${timeStr} (+${details.elapsed})`; + if (details.duration && details.duration > 1000) { + line += ` • took ${formatDuration(details.duration)}`; + } + + const box = new Box(0, 0, undefined); + box.addChild(new Text(theme.fg("dim", line), 0, 0)); + return box; + }); + // Start timer on session start pi.on("session_start", async (_event, ctx) => { sessionStart = Date.now(); turnStartTime = null; lastTurnDuration = null; - // Clear any existing timer if (timerHandle) clearInterval(timerHandle); - - // Update status every second timerHandle = setInterval(() => updateStatus(ctx), 1000); updateStatus(ctx); }); @@ -71,6 +100,35 @@ export default function (pi: ExtensionAPI) { turnStartTime = null; updateStatus(ctx); } + + if (lastTurnDuration !== null) { + const now = Date.now(); + const elapsed = formatElapsed(now - sessionStart); + pi.sendMessage( + { + customType: "timestamp-suffix", + content: "", + display: true, + details: { timestamp: now, elapsed, duration: lastTurnDuration }, + }, + { deliverAs: "followUp", triggerTurn: false } + ); + } + }); + + // Inject timestamp marker right before agent starts + pi.on("before_agent_start", async (event) => { + const now = Date.now(); + const elapsed = formatElapsed(now - sessionStart); + + return { + message: { + customType: "timestamp-prefix", + content: "", + display: true, + details: { timestamp: now, elapsed }, + }, + }; }); // Clean up on shutdown diff --git a/pi/files/agent/settings.json b/pi/files/agent/settings.json index b77c960..75c1172 100644 --- a/pi/files/agent/settings.json +++ b/pi/files/agent/settings.json @@ -1,7 +1,7 @@ { "lastChangelogVersion": "0.55.4", "defaultProvider": "openrouter", - "defaultModel": "moonshotai/kimi-k2.5", + "defaultModel": "openai/gpt-5.2-codex", "defaultThinkingLevel": "off", "theme": "matugen" } \ No newline at end of file