aboutsummaryrefslogtreecommitdiffstats
path: root/pi-ext
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-22 23:52:20 +0200
committeruser <user@clank>2026-08-12 19:34:35 +0200
commitd8e28dc347f57a8b4b690572b854cdded0345bc6 (patch)
treefe0aed2af01461dcab9d7a5c424ae7a93c78938e /pi-ext
parentAdd audit-fanout.ts: parallel read-only auditor subagents with live status (diff)
downloadsl0ppi-d8e28dc347f57a8b4b690572b854cdded0345bc6.tar.gz
sl0ppi-d8e28dc347f57a8b4b690572b854cdded0345bc6.tar.xz
sl0ppi-d8e28dc347f57a8b4b690572b854cdded0345bc6.zip
slop-tool-boxes: stamp bash runtime in bottom border (right-aligned)
Track per-toolCallId execution timing via tool_execution_start/end and render the elapsed duration into the bottom border of bash rows, aligned to the right and kept one dash clear of the corner. Respects the no-forced-repaint invariant: the live timer is `end ?? Date.now()`, so it only advances on renders that already happen (bash output streaming re-renders the result slot border); the final duration is stamped once the row settles. Muted time text sits outside the pulsing dash paint. Gated to bash via RUNTIME_TOOLS (extensible).
Diffstat (limited to 'pi-ext')
-rw-r--r--pi-ext/slop-tool-boxes.ts49
1 files changed, 46 insertions, 3 deletions
diff --git a/pi-ext/slop-tool-boxes.ts b/pi-ext/slop-tool-boxes.ts
index 3a0a3ee..ca1b66b 100644
--- a/pi-ext/slop-tool-boxes.ts
+++ b/pi-ext/slop-tool-boxes.ts
@@ -110,6 +110,33 @@ function fgRGB(rgb: [number, number, number], s: string): string {
const activeIds = new Set<string>(); // toolCallIds executing in THIS session
const rowsById = new Map<string, FrameState>(); // toolCallId -> row state (start/end nudges)
+// toolCallId -> execution timing. `end` is set on tool_execution_end; while a
+// tool is still running `end` is undefined and elapsed is measured live (only
+// advancing on renders that were going to happen anyway — no forced repaint).
+const timingById = new Map<string, { start: number; end?: number }>();
+
+// Tools that get a right-aligned runtime stamped into their bottom border.
+const RUNTIME_TOOLS = new Set<string>(["bash"]);
+
+function fmtDuration(ms: number): string {
+ if (ms < 1000) return `${Math.round(ms)}ms`;
+ const s = ms / 1000;
+ if (s < 10) return `${s.toFixed(1)}s`;
+ if (s < 60) return `${Math.round(s)}s`;
+ const m = Math.floor(s / 60);
+ const rem = Math.round(s % 60);
+ return `${m}m${rem}s`;
+}
+
+// Painted ` 1.2s ` label for the bottom border, or undefined if untimed.
+function runtimeLabel(st: FrameState, theme: Theme): string | undefined {
+ const id = st.toolCallId;
+ if (!id) return undefined;
+ const t = timingById.get(id);
+ if (!t) return undefined;
+ const ms = (t.end ?? Date.now()) - t.start;
+ return " " + theme.fg("muted", fmtDuration(ms)) + " ";
+}
function pulsePaint(): Paint {
// Pure function of wall-clock time — no counter, no timer.
@@ -265,8 +292,19 @@ function frameTop(width: number, label: string, paint: Paint): string {
);
}
-function frameBottom(width: number, paint: Paint): string {
+// Bottom border, optionally with an already-painted `label` stamped near the
+// right edge (kept clear of the corner by one trailing dash). Falls back to a
+// plain border if the label can't fit.
+function frameBottom(width: number, paint: Paint, label?: string): string {
const innerWidth = Math.max(0, width - 2);
+ if (label) {
+ const segW = visibleWidth(label);
+ const rightLen = 1;
+ if (segW + rightLen + 1 <= innerWidth) {
+ const leftLen = innerWidth - segW - rightLen;
+ return paint(BL + H.repeat(leftLen)) + label + paint(H.repeat(rightLen) + BR);
+ }
+ }
return paint(BL + H.repeat(innerWidth) + BR);
}
@@ -400,11 +438,12 @@ function registerFramedTool(pi: ExtensionAPI, def: ToolDef) {
// repaint while a tool streams output (edit/write) and reads as violent
// flicker. Keeping rails static means streaming only redraws the new
// content line, and the pulse only touches 1-2 border lines. (NOTES.md)
+ const rt = RUNTIME_TOOLS.has(displayName) ? runtimeLabel(st, theme) : undefined;
const build = (border: Paint, rail: string): string[] => {
const out = [frameTop(width, label, border), ...frameBody(inner, width, rail)];
// While the result slot hasn't started rendering yet (still-running
// tool), close the box here so the frame is never bottomless.
- if (!st.resultActive) out.push(frameBottom(width, border));
+ if (!st.resultActive) out.push(frameBottom(width, border, rt));
return out;
};
if (activeNow) {
@@ -479,9 +518,10 @@ function registerFramedTool(pi: ExtensionAPI, def: ToolDef) {
const railStr = activeNow
? theme.fg("muted", DOTTED_RAIL)
: theme.fg(borderColorFor(st.status), V);
+ const rt = RUNTIME_TOOLS.has(displayName) ? runtimeLabel(st, theme) : undefined;
const build = (border: Paint): string[] => [
...frameBody(inner, width, railStr),
- frameBottom(width, border),
+ frameBottom(width, border, rt),
];
if (activeNow) {
cw = -1;
@@ -525,10 +565,13 @@ export default function (pi: ExtensionAPI) {
};
pi.on("tool_execution_start", (event: any) => {
markActive(event.toolCallId);
+ if (event.toolCallId) timingById.set(event.toolCallId, { start: Date.now() });
refresh(event.toolCallId);
});
pi.on("tool_execution_end", (event: any) => {
clearActive(event.toolCallId);
+ const t = event.toolCallId ? timingById.get(event.toolCallId) : undefined;
+ if (t) t.end = Date.now();
refresh(event.toolCallId);
});