timestamps are awesome

This commit is contained in:
2026-03-04 14:54:05 +00:00
parent adc4e63155
commit d7ee766f21
2 changed files with 74 additions and 16 deletions
+73 -15
View File
@@ -2,29 +2,36 @@
* Timestamps extension for Pi. * Timestamps extension for Pi.
* *
* - Shows elapsed session time in footer (updates every second) * - 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 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 sessionStart = Date.now();
let timerHandle: ReturnType<typeof setInterval> | null = null; let timerHandle: ReturnType<typeof setInterval> | null = null;
let turnStartTime: number | null = null; let turnStartTime: number | null = null;
let lastTurnDuration: 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 { function formatElapsed(ms: number): string {
const seconds = Math.floor(ms / 1000); const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60); const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60); const hours = Math.floor(minutes / 60);
if (hours > 0) { if (hours > 0) return `${hours}h ${minutes % 60}m`;
return `${hours}h ${minutes % 60}m`; if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
} else if (minutes > 0) { return `${seconds}s`;
return `${minutes}m ${seconds % 60}s`;
} else {
return `${seconds}s`;
}
} }
function formatDuration(ms: number): string { 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 updateStatus = (ctx: { ui: { setStatus: (id: string, text: string | undefined) => void; theme: { fg: (color: string, text: string) => string } } }) => {
const elapsed = Date.now() - sessionStart; const elapsed = Date.now() - sessionStart;
let status = ctx.ui.theme.fg("dim", `${formatElapsed(elapsed)}`); let status = ctx.ui.theme.fg("dim", `${formatElapsed(elapsed)}`);
// Show last turn duration if available
if (lastTurnDuration !== null) { if (lastTurnDuration !== null) {
status += ctx.ui.theme.fg("muted", ` | took ${formatDuration(lastTurnDuration)}`); status += ctx.ui.theme.fg("muted", ` | took ${formatDuration(lastTurnDuration)}`);
} }
ctx.ui.setStatus("timestamps", status); 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 // Start timer on session start
pi.on("session_start", async (_event, ctx) => { pi.on("session_start", async (_event, ctx) => {
sessionStart = Date.now(); sessionStart = Date.now();
turnStartTime = null; turnStartTime = null;
lastTurnDuration = null; lastTurnDuration = null;
// Clear any existing timer
if (timerHandle) clearInterval(timerHandle); if (timerHandle) clearInterval(timerHandle);
// Update status every second
timerHandle = setInterval(() => updateStatus(ctx), 1000); timerHandle = setInterval(() => updateStatus(ctx), 1000);
updateStatus(ctx); updateStatus(ctx);
}); });
@@ -71,6 +100,35 @@ export default function (pi: ExtensionAPI) {
turnStartTime = null; turnStartTime = null;
updateStatus(ctx); 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 // Clean up on shutdown
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"lastChangelogVersion": "0.55.4", "lastChangelogVersion": "0.55.4",
"defaultProvider": "openrouter", "defaultProvider": "openrouter",
"defaultModel": "moonshotai/kimi-k2.5", "defaultModel": "openai/gpt-5.2-codex",
"defaultThinkingLevel": "off", "defaultThinkingLevel": "off",
"theme": "matugen" "theme": "matugen"
} }