@pi-unipi/footer 2.12.1 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +3 -2
- package/src/index.ts +7 -5
- package/src/process-line.ts +74 -0
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ An experimental input surface, on by default and toggleable in `/unipi:footer-se
|
|
|
18
18
|
- **Top border:** animated lolcat-gradient UNIPI brand + git branch (turns rainbow-frame animated while thinking is max/xhigh)
|
|
19
19
|
- **Bottom border:** workspace · context %/window · model · thinking level
|
|
20
20
|
- **Session strip:** turns/steps, wall + tool wall time, average TTFT, tok/s, cache hit % — colored per stat, honest across restarts (derived from persisted session timestamps when live hooks are unavailable; provider-reported `usage.output` anchors token counts whenever present)
|
|
21
|
+
- **Process line (new in 2.13):** centered one-liner directly above the frame while background work is in flight — `● 3 running ● 1 stopped ● 1 failed ● 2 done` — green ● running, yellow ● stopped (killed), red ● failed, gray ● done. Covers every task type (shell jobs, delegates, fusion workflows) via direct registry reads; zero-count buckets are omitted and the line hides when idle. Counts reset per session.
|
|
21
22
|
- The classic segment status line is suppressed while glance mode is on; toggle it back for the classic footer
|
|
22
23
|
|
|
23
24
|
## Commands
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/footer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "Persistent status bar for Unipi — subscribes to UNIPI_EVENTS and renders key stats from all unipi packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@pi-unipi/core": "2.12.0"
|
|
35
|
+
"@pi-unipi/core": "2.12.0",
|
|
36
|
+
"@pi-unipi/background-tasks": "2.13.0"
|
|
36
37
|
},
|
|
37
38
|
"peerDependencies": {
|
|
38
39
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { STATUS_EXT_SEGMENTS } from "./segments/status-ext.js";
|
|
|
29
29
|
|
|
30
30
|
import type { FooterGroup, FooterSegment } from "./types.js";
|
|
31
31
|
import { tpsTracker } from "./tps-tracker.js";
|
|
32
|
+
import { renderProcessLine } from "./process-line.js";
|
|
32
33
|
|
|
33
34
|
/** All segment groups */
|
|
34
35
|
const ALL_GROUPS: FooterGroup[] = [
|
|
@@ -328,9 +329,10 @@ function setupFooterUI(pi: ExtensionAPI, ctx: ExtensionContext, state: FooterSta
|
|
|
328
329
|
};
|
|
329
330
|
});
|
|
330
331
|
|
|
331
|
-
// Top row widget —
|
|
332
|
-
//
|
|
333
|
-
//
|
|
332
|
+
// Top row widget — dual role. Classic mode: status segment line. Glance
|
|
333
|
+
// mode: the bg-process one-liner, rendered directly above the glance frame
|
|
334
|
+
// (the frame replaces the editor, so this aboveEditor slot sits right above
|
|
335
|
+
// the footer); the frame's own borders show branch/context/model/thinking.
|
|
334
336
|
ctx.ui.setWidget("footer-top", (_tui, theme) => {
|
|
335
337
|
// Update the renderer's theme-like
|
|
336
338
|
const themeLike = { fg: (color: string, text: string) => theme.fg(color as any, text) };
|
|
@@ -344,8 +346,8 @@ function setupFooterUI(pi: ExtensionAPI, ctx: ExtensionContext, state: FooterSta
|
|
|
344
346
|
},
|
|
345
347
|
render(width: number): string[] {
|
|
346
348
|
if (!state.enabled || !state.piContext || width <= 0) return [];
|
|
347
|
-
// Glance mode
|
|
348
|
-
if (state.glanceMode) return
|
|
349
|
+
// Glance mode: this slot becomes the bg-process one-liner.
|
|
350
|
+
if (state.glanceMode) return renderProcessLine(width);
|
|
349
351
|
const layout = state.renderer.computeLayout(width);
|
|
350
352
|
if (!layout.topContent) return [];
|
|
351
353
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/footer — Background process one-liner
|
|
3
|
+
*
|
|
4
|
+
* Glance-mode strip rendered above the footer frame: one colored dot + count
|
|
5
|
+
* per background-task status. Reads DIRECTLY from the
|
|
6
|
+
* @pi-unipi/background-tasks shared registry (no events, no polling
|
|
7
|
+
* channels); re-renders on the footer's existing 1s refresh timer.
|
|
8
|
+
*
|
|
9
|
+
* Dot → status mapping:
|
|
10
|
+
* green ● running yellow ● stopped (killed) red ● failed gray ● done (completed)
|
|
11
|
+
*
|
|
12
|
+
* Buckets with zero count are omitted; with nothing in flight the line is
|
|
13
|
+
* empty so the footer stays clean.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
17
|
+
import { getSharedTaskRegistry } from "@pi-unipi/background-tasks";
|
|
18
|
+
|
|
19
|
+
const GREEN_DOT = "\x1b[38;5;82m●\x1b[0m"; // running — active work
|
|
20
|
+
const YELLOW_DOT = "\x1b[38;5;220m●\x1b[0m"; // stopped (killed) — needs attention
|
|
21
|
+
const RED_DOT = "\x1b[38;5;196m●\x1b[0m"; // failed — needs attention
|
|
22
|
+
const GRAY_DOT = "\x1b[38;5;245m●\x1b[0m"; // done (completed) — idle info
|
|
23
|
+
|
|
24
|
+
export interface BgProcessCounts {
|
|
25
|
+
running: number;
|
|
26
|
+
stopped: number;
|
|
27
|
+
failed: number;
|
|
28
|
+
done: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Count background tasks by display status straight from the registry.
|
|
33
|
+
* Returns null when background-tasks has not published a registry (module
|
|
34
|
+
* disabled, before first load, or after session shutdown).
|
|
35
|
+
*/
|
|
36
|
+
export function countBgProcesses(): BgProcessCounts | null {
|
|
37
|
+
try {
|
|
38
|
+
const tasks = getSharedTaskRegistry()?.allTasks();
|
|
39
|
+
if (!tasks) return null;
|
|
40
|
+
const counts: BgProcessCounts = { running: 0, stopped: 0, failed: 0, done: 0 };
|
|
41
|
+
for (const task of tasks) {
|
|
42
|
+
if (task.status === "running") counts.running++;
|
|
43
|
+
else if (task.status === "killed") counts.stopped++;
|
|
44
|
+
else if (task.status === "failed") counts.failed++;
|
|
45
|
+
else if (task.status === "completed") counts.done++;
|
|
46
|
+
}
|
|
47
|
+
return counts;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Render the centered one-liner for the given terminal width.
|
|
55
|
+
* Returns [] when there is nothing to show.
|
|
56
|
+
*/
|
|
57
|
+
export function renderProcessLine(width: number): string[] {
|
|
58
|
+
if (width <= 0) return [];
|
|
59
|
+
const counts = countBgProcesses();
|
|
60
|
+
if (!counts) return [];
|
|
61
|
+
|
|
62
|
+
const parts: string[] = [];
|
|
63
|
+
if (counts.running > 0) parts.push(`${GREEN_DOT} ${counts.running} running`);
|
|
64
|
+
if (counts.stopped > 0) parts.push(`${YELLOW_DOT} ${counts.stopped} stopped`);
|
|
65
|
+
if (counts.failed > 0) parts.push(`${RED_DOT} ${counts.failed} failed`);
|
|
66
|
+
if (counts.done > 0) parts.push(`${GRAY_DOT} ${counts.done} done`);
|
|
67
|
+
if (parts.length === 0) return [];
|
|
68
|
+
|
|
69
|
+
const line = parts.join(" ");
|
|
70
|
+
const w = visibleWidth(line);
|
|
71
|
+
if (w >= width) return [truncateToWidth(line, width)];
|
|
72
|
+
const leftPad = Math.floor((width - w) / 2);
|
|
73
|
+
return [" ".repeat(leftPad) + line];
|
|
74
|
+
}
|