diff options
| author | user <user@clank> | 2026-07-22 23:52:20 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-12 19:34:35 +0200 |
| commit | d8e28dc347f57a8b4b690572b854cdded0345bc6 (patch) | |
| tree | fe0aed2af01461dcab9d7a5c424ae7a93c78938e /pi-ext | |
| parent | Add audit-fanout.ts: parallel read-only auditor subagents with live status (diff) | |
| download | sl0ppi-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.ts | 49 |
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); }); |
