@pi-unipi/footer 3.0.0-alpha.3 → 3.0.0-alpha.5
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 +4 -6
- package/package.json +3 -3
- package/src/help.ts +1 -1
- package/src/index.ts +33 -2
- package/src/presets.ts +3 -4
- package/src/rendering/icons.ts +0 -20
- package/src/segments/compactor.ts +52 -142
- package/src/segments/status-ext.ts +0 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ Footer subscribes to events from every Unipi package:
|
|
|
37
37
|
| Group | Events | Segments |
|
|
38
38
|
|-------|--------|----------|
|
|
39
39
|
| core | Pi SDK | model, thinking, path, git, context_pct, cost, tokens, session |
|
|
40
|
-
| compactor | Pi session data
|
|
40
|
+
| compactor | Pi session data | compactions — `cmp 4× 39k→13k · 3m` (count, tokens before → after, time since the last; hidden until the first) |
|
|
41
41
|
| memory | `MEMORY_STORED`/`DELETED`/`CONSOLIDATED` | project_count, total_count, consolidations |
|
|
42
42
|
| mcp | `MCP_SERVER_STARTED`/`STOPPED`/`ERROR` | servers_total, servers_active, tools_total |
|
|
43
43
|
| ralph | `RALPH_LOOP_START`/`END`/`ITERATION_DONE` | active_loops, total_iterations, loop_status |
|
|
@@ -90,9 +90,7 @@ Settings in `~/.pi/agent/settings.json` under `unipi.footer`:
|
|
|
90
90
|
"compactor": {
|
|
91
91
|
"show": true,
|
|
92
92
|
"segments": {
|
|
93
|
-
"
|
|
94
|
-
"compactions": true,
|
|
95
|
-
"tokens_saved": true
|
|
93
|
+
"compactions": true
|
|
96
94
|
}
|
|
97
95
|
}
|
|
98
96
|
}
|
|
@@ -137,11 +135,11 @@ When `iconStyle` is not set, footer auto-detects Nerd Font support and defaults
|
|
|
137
135
|
|
|
138
136
|
```
|
|
139
137
|
Wide terminal (>120 cols):
|
|
140
|
-
model | thinking | path | git | context |
|
|
138
|
+
model | thinking | path | git | context | compactions | cost | project_count
|
|
141
139
|
|
|
142
140
|
Narrow terminal (<120 cols):
|
|
143
141
|
Row 1: model | thinking | path | git | context | cost
|
|
144
|
-
Row 2: compactions |
|
|
142
|
+
Row 2: compactions | project_count | ralph | workflow
|
|
145
143
|
```
|
|
146
144
|
|
|
147
145
|
## License
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/footer",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.5",
|
|
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,8 +32,8 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
36
|
-
"@pi-unipi/background-tasks": "3.0.0-alpha.
|
|
35
|
+
"@pi-unipi/core": "3.0.0-alpha.5",
|
|
36
|
+
"@pi-unipi/background-tasks": "3.0.0-alpha.5"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
package/src/help.ts
CHANGED
|
@@ -75,7 +75,7 @@ function buildHelpLines(
|
|
|
75
75
|
export function getGroupForSegment(segId: string): string {
|
|
76
76
|
const coreIds = ["model", "api_state", "tool_count", "git", "context_pct", "cost", "tokens_total", "tokens_in", "tokens_out", "session", "hostname", "time", "tps", "clock", "duration", "thinking_level", "uni", "directory"];
|
|
77
77
|
if (coreIds.includes(segId)) return "core";
|
|
78
|
-
const compactorIds = ["
|
|
78
|
+
const compactorIds = ["compactions"];
|
|
79
79
|
if (compactorIds.includes(segId)) return "compactor";
|
|
80
80
|
if (["project_count", "total_count", "consolidations"].includes(segId)) return "memory";
|
|
81
81
|
if (["servers_total", "servers_active", "tools_total", "servers_failed"].includes(segId)) return "mcp";
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { GlanceEditor } from "./glance-editor.js";
|
|
|
18
18
|
|
|
19
19
|
// Import segment groups
|
|
20
20
|
import { CORE_SEGMENTS } from "./segments/core.js";
|
|
21
|
-
import { COMPACTOR_SEGMENTS } from "./segments/compactor.js";
|
|
21
|
+
import { COMPACTOR_SEGMENTS, compactionSummary } from "./segments/compactor.js";
|
|
22
22
|
import { MEMORY_SEGMENTS } from "./segments/memory.js";
|
|
23
23
|
import { MCP_SEGMENTS } from "./segments/mcp.js";
|
|
24
24
|
import { RALPH_SEGMENTS } from "./segments/ralph.js";
|
|
@@ -546,14 +546,30 @@ const STRIP_COLOR = {
|
|
|
546
546
|
psMid: "\x1b[97m", // white — in between
|
|
547
547
|
cacheHit: "\x1b[92m", // green — high hit
|
|
548
548
|
cacheWarn: "\x1b[93m", // amber — lowish hit
|
|
549
|
+
compact: "\x1b[94m", // blue — compactions
|
|
549
550
|
reset: "\x1b[39m",
|
|
550
551
|
} as const;
|
|
551
552
|
|
|
552
553
|
const c = (code: string, text: string) => `${code}${text}${STRIP_COLOR.reset}`;
|
|
553
554
|
|
|
555
|
+
function fmtTokensShort(n: number): string {
|
|
556
|
+
if (n < 1000) return String(n);
|
|
557
|
+
if (n < 10000) return `${(n / 1000).toFixed(1)}k`;
|
|
558
|
+
if (n < 1000000) return `${Math.round(n / 1000)}k`;
|
|
559
|
+
return `${(n / 1000000).toFixed(1)}M`;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function agoShort(ms: number): string {
|
|
563
|
+
const sec = Math.max(0, Math.round(ms / 1000));
|
|
564
|
+
if (sec < 60) return "just now";
|
|
565
|
+
if (sec < 3600) return `${Math.round(sec / 60)}m ago`;
|
|
566
|
+
if (sec < 86400) return `${Math.round(sec / 3600)}h ago`;
|
|
567
|
+
return `${Math.round(sec / 86400)}d ago`;
|
|
568
|
+
}
|
|
569
|
+
|
|
554
570
|
/**
|
|
555
571
|
* Glance-style centered stats strip under the input:
|
|
556
|
-
* n Turn · n Steps | wall · tool wall | avg TTFT · n tok/s | cache n%
|
|
572
|
+
* n Turn · n Steps | wall · tool wall | avg TTFT · n tok/s | n compactions · 39k→13k · 3m ago | cache n%
|
|
557
573
|
*/
|
|
558
574
|
function renderSessionStrip(piContext: unknown): string | null {
|
|
559
575
|
const parts: string[] = [];
|
|
@@ -588,6 +604,21 @@ function renderSessionStrip(piContext: unknown): string | null {
|
|
|
588
604
|
if (seg) parts.push(seg);
|
|
589
605
|
}
|
|
590
606
|
|
|
607
|
+
// Compactions: hidden until the first one.
|
|
608
|
+
const branch = (() => {
|
|
609
|
+
try {
|
|
610
|
+
return ((piContext as any)?.sessionManager?.getBranch?.() ?? []) as unknown[];
|
|
611
|
+
} catch {
|
|
612
|
+
return [];
|
|
613
|
+
}
|
|
614
|
+
})();
|
|
615
|
+
const cmp = compactionSummary(branch);
|
|
616
|
+
if (cmp.count > 0) {
|
|
617
|
+
const sizes = cmp.before > 0 ? ` \u00b7 ${c(STRIP_COLOR.compact, `${fmtTokensShort(cmp.before)}\u2192${fmtTokensShort(cmp.after)}`)}` : "";
|
|
618
|
+
const age = cmp.lastAt != null ? ` \u00b7 ${agoShort(Date.now() - cmp.lastAt)}` : "";
|
|
619
|
+
parts.push(`${c(STRIP_COLOR.compact, String(cmp.count))} compaction${cmp.count === 1 ? "" : "s"}${sizes}${age}`);
|
|
620
|
+
}
|
|
621
|
+
|
|
591
622
|
const hit = cacheHitPct(piContext);
|
|
592
623
|
if (hit !== null) {
|
|
593
624
|
const hitColor = hit >= 70 ? STRIP_COLOR.cacheHit : STRIP_COLOR.cacheWarn;
|
package/src/presets.ts
CHANGED
|
@@ -18,7 +18,7 @@ const DEFAULT_PRESET: PresetDef = {
|
|
|
18
18
|
"uni", "lh_mode", "model", "thinking_level", "directory", "git",
|
|
19
19
|
],
|
|
20
20
|
rightSegments: [
|
|
21
|
-
"context_pct", "tokens_total",
|
|
21
|
+
"context_pct", "compactions", "tokens_total",
|
|
22
22
|
"tps", "cost",
|
|
23
23
|
"clock", "duration",
|
|
24
24
|
],
|
|
@@ -33,7 +33,7 @@ const CLASSIC_PRESET: PresetDef = {
|
|
|
33
33
|
],
|
|
34
34
|
rightSegments: [
|
|
35
35
|
"tps", "context_pct", "cost",
|
|
36
|
-
"compactions", "
|
|
36
|
+
"compactions", "project_count",
|
|
37
37
|
"current_command", "loop_status", "extension_statuses",
|
|
38
38
|
"clock", "duration",
|
|
39
39
|
],
|
|
@@ -76,7 +76,7 @@ const FULL_PRESET: PresetDef = {
|
|
|
76
76
|
],
|
|
77
77
|
rightSegments: [
|
|
78
78
|
"tps", "context_pct", "cost", "tokens_total",
|
|
79
|
-
"
|
|
79
|
+
"compactions",
|
|
80
80
|
"project_count", "total_count",
|
|
81
81
|
"servers_total", "servers_active", "tools_total",
|
|
82
82
|
"active_loops", "loop_status",
|
|
@@ -87,7 +87,6 @@ const FULL_PRESET: PresetDef = {
|
|
|
87
87
|
secondarySegments: [
|
|
88
88
|
"hostname",
|
|
89
89
|
"tokens_in", "tokens_out",
|
|
90
|
-
"compression_ratio",
|
|
91
90
|
"platforms_enabled", "last_sent",
|
|
92
91
|
"thinking_level",
|
|
93
92
|
],
|
package/src/rendering/icons.ts
CHANGED
|
@@ -35,13 +35,8 @@ export interface IconSet {
|
|
|
35
35
|
directory: string;
|
|
36
36
|
|
|
37
37
|
// Compactor segments
|
|
38
|
-
sessionEvents: string;
|
|
39
|
-
compactions: string;
|
|
40
|
-
tokensSaved: string;
|
|
41
|
-
compressionRatio: string;
|
|
42
38
|
indexedDocs: string;
|
|
43
39
|
sandboxRuns: string;
|
|
44
|
-
searchQueries: string;
|
|
45
40
|
|
|
46
41
|
// Memory segments
|
|
47
42
|
projectCount: string;
|
|
@@ -106,13 +101,8 @@ export const NERD_ICONS: IconSet = {
|
|
|
106
101
|
thinkingLevel: "\uF400", //
|
|
107
102
|
|
|
108
103
|
// Compactor
|
|
109
|
-
sessionEvents: "\uEA86", //
|
|
110
|
-
compactions: "\u{F0C8F}", //
|
|
111
|
-
tokensSaved: "\uF155", // (kept — missing from customization)
|
|
112
|
-
compressionRatio:"\u{F0C8F}", //
|
|
113
104
|
indexedDocs: "\u{F0219}", //
|
|
114
105
|
sandboxRuns: "\uF233", //
|
|
115
|
-
searchQueries: "\uF002", //
|
|
116
106
|
|
|
117
107
|
// Memory
|
|
118
108
|
projectCount: "\uEE9C", //
|
|
@@ -177,13 +167,8 @@ export const EMOJI_ICONS: IconSet = {
|
|
|
177
167
|
directory: "📁",
|
|
178
168
|
|
|
179
169
|
// Compactor
|
|
180
|
-
sessionEvents: "📈",
|
|
181
|
-
compactions: "🗜️",
|
|
182
|
-
tokensSaved: "💲",
|
|
183
|
-
compressionRatio:"📐",
|
|
184
170
|
indexedDocs: "📑",
|
|
185
171
|
sandboxRuns: "▶️",
|
|
186
|
-
searchQueries: "🔍",
|
|
187
172
|
|
|
188
173
|
// Memory
|
|
189
174
|
projectCount: "🧠",
|
|
@@ -248,13 +233,8 @@ export const TEXT_ICONS: IconSet = {
|
|
|
248
233
|
directory: "DIR",
|
|
249
234
|
|
|
250
235
|
// Compactor
|
|
251
|
-
sessionEvents: "EVT",
|
|
252
|
-
compactions: "CMP",
|
|
253
|
-
tokensSaved: "SVD",
|
|
254
|
-
compressionRatio:"RAT",
|
|
255
236
|
indexedDocs: "IDX",
|
|
256
237
|
sandboxRuns: "SBX",
|
|
257
|
-
searchQueries: "QRY",
|
|
258
238
|
|
|
259
239
|
// Memory
|
|
260
240
|
projectCount: "MEM",
|
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @pi-unipi/footer — Compactor
|
|
2
|
+
* @pi-unipi/footer — Compactor segment
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* tokens_saved, compression_ratio, sandbox_runs, search_queries.
|
|
4
|
+
* One dense, icon-free segment, shown once the session has compacted:
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* cmp 4× 39k→13k · 3m
|
|
7
|
+
*
|
|
8
|
+
* count, tokens before → after across all compactions, time since the last.
|
|
9
|
+
* Read from the session branch (Pi's compaction entries).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { FooterSegment, FooterSegmentContext, RenderedSegment } from "../types.js";
|
|
13
|
-
import { applyColor
|
|
14
|
-
import { getIcon } from "../rendering/icons.js";
|
|
15
|
-
import { isSegmentEnabled } from "../config.js";
|
|
16
|
-
|
|
17
|
-
function withIcon(segmentId: string, text: string): string {
|
|
18
|
-
const icon = getIcon(segmentId);
|
|
19
|
-
return icon ? `${icon} ${text}` : text;
|
|
20
|
-
}
|
|
13
|
+
import { applyColor } from "../rendering/theme.js";
|
|
21
14
|
|
|
22
15
|
function formatTokens(n: number): string {
|
|
23
16
|
if (n < 1000) return n.toString();
|
|
@@ -26,149 +19,66 @@ function formatTokens(n: number): string {
|
|
|
26
19
|
return `${(n / 1000000).toFixed(1)}M`;
|
|
27
20
|
}
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
function ago(ms: number): string {
|
|
23
|
+
const s = Math.max(0, Math.round(ms / 1000));
|
|
24
|
+
if (s < 60) return "now";
|
|
25
|
+
if (s < 3600) return `${Math.round(s / 60)}m`;
|
|
26
|
+
if (s < 86400) return `${Math.round(s / 3600)}h`;
|
|
27
|
+
return `${Math.round(s / 86400)}d`;
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
/**
|
|
35
|
-
function
|
|
36
|
-
const
|
|
37
|
-
|
|
30
|
+
/** Tokens left after a compaction: our recorded estimate, else the summary size. */
|
|
31
|
+
function tokensAfterOf(entry: any, before: number): number {
|
|
32
|
+
const recorded = Number(entry?.details?.tokensAfter ?? 0);
|
|
33
|
+
if (recorded > 0) return Math.min(recorded, before);
|
|
34
|
+
const summaryChars = typeof entry?.summary === "string" ? entry.summary.length : 0;
|
|
35
|
+
return Math.min(before, Math.ceil(summaryChars / 4));
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const sm = getSessionManager(ctx);
|
|
43
|
-
if (!sm || typeof sm.getBranch !== "function") return [];
|
|
38
|
+
function branchOf(ctx: FooterSegmentContext): any[] {
|
|
39
|
+
const sm = (ctx.piContext as Record<string, unknown> | undefined)?.sessionManager as any;
|
|
44
40
|
try {
|
|
45
|
-
return sm.getBranch() ?? [];
|
|
41
|
+
return typeof sm?.getBranch === "function" ? (sm.getBranch() ?? []) : [];
|
|
46
42
|
} catch {
|
|
47
43
|
return [];
|
|
48
44
|
}
|
|
49
45
|
}
|
|
50
46
|
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
export function compactionSummary(branch: readonly any[], now = Date.now()): { count: number; before: number; after: number; lastAt?: number } {
|
|
48
|
+
let count = 0;
|
|
49
|
+
let before = 0;
|
|
50
|
+
let after = 0;
|
|
51
|
+
let lastAt: number | undefined;
|
|
52
|
+
for (const e of branch) {
|
|
53
|
+
if (e?.type !== "compaction") continue;
|
|
54
|
+
count++;
|
|
55
|
+
const b = Number(e.tokensBefore ?? 0);
|
|
56
|
+
before += b;
|
|
57
|
+
after += tokensAfterOf(e, b);
|
|
58
|
+
const at = Date.parse(e.timestamp ?? "");
|
|
59
|
+
if (Number.isFinite(at) && at <= now) lastAt = at;
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
-
const content = withIcon("sessionEvents", `${count}`);
|
|
62
|
-
return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
|
|
61
|
+
return { count, before, after, ...(lastAt != null ? { lastAt } : {}) };
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
function renderCompactionsSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (compactionCount === 0) {
|
|
76
|
-
if (isSegmentEnabled("compactor", "compactions")) {
|
|
77
|
-
return { content: mutedPlaceholder(withIcon("compactions", "0")), visible: true };
|
|
78
|
-
}
|
|
79
|
-
return hidden();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const content = withIcon("compactions", `${compactionCount}`);
|
|
83
|
-
return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function renderTokensSavedSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
87
|
-
// Sum tokens saved from compaction entries.
|
|
88
|
-
// Pi's CompactionEntry has tokensBefore (total tokens before compaction).
|
|
89
|
-
// Compaction keeps ~10-15% of context, so tokens saved ≈ tokensBefore × 0.85.
|
|
90
|
-
const events = getSessionEvents(ctx);
|
|
91
|
-
let tokensSaved = 0;
|
|
92
|
-
let hasCompaction = false;
|
|
93
|
-
for (const e of events) {
|
|
94
|
-
if (!e || typeof e !== "object") continue;
|
|
95
|
-
if (e.type === "compaction") {
|
|
96
|
-
hasCompaction = true;
|
|
97
|
-
const tokensBefore = Number(e.tokensBefore ?? 0);
|
|
98
|
-
// Estimate tokens kept at ~12% (compaction summary + recent messages)
|
|
99
|
-
const tokensAfter = Math.round(tokensBefore * 0.12);
|
|
100
|
-
tokensSaved += Math.max(0, tokensBefore - tokensAfter);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (!hasCompaction || tokensSaved === 0) return hidden();
|
|
104
|
-
|
|
105
|
-
const content = withIcon("tokensSaved", formatTokens(tokensSaved));
|
|
106
|
-
return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function renderCompressionRatioSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
110
|
-
// Calculate compression ratio from Pi's CompactionEntry.tokensBefore.
|
|
111
|
-
// Compaction keeps ~12% of context, giving ~8:1 compression.
|
|
112
|
-
const events = getSessionEvents(ctx);
|
|
113
|
-
let totalBefore = 0;
|
|
114
|
-
let totalAfter = 0;
|
|
115
|
-
for (const e of events) {
|
|
116
|
-
if (!e || typeof e !== "object") continue;
|
|
117
|
-
if (e.type === "compaction") {
|
|
118
|
-
const before = Number(e.tokensBefore ?? 0);
|
|
119
|
-
if (before > 0) {
|
|
120
|
-
totalBefore += before;
|
|
121
|
-
totalAfter += Math.round(before * 0.12);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (totalBefore === 0 || totalAfter === 0) return hidden();
|
|
126
|
-
|
|
127
|
-
const ratio = totalBefore / totalAfter;
|
|
128
|
-
const content = withIcon("compressionRatio", `${ratio.toFixed(1)}x`);
|
|
129
|
-
return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function renderSandboxRunsSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
133
|
-
// Count sandbox events from session manager branch
|
|
134
|
-
const events = getSessionEvents(ctx);
|
|
135
|
-
let sandboxCount = 0;
|
|
136
|
-
for (const e of events) {
|
|
137
|
-
if (!e || typeof e !== "object") continue;
|
|
138
|
-
// Count tool calls that are sandbox/execute tools
|
|
139
|
-
const name = String((e as any).name ?? "").toLowerCase();
|
|
140
|
-
if (name.includes("sandbox") || name.includes("ctx_execute") || name === "execute") {
|
|
141
|
-
sandboxCount++;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (sandboxCount === 0) return hidden();
|
|
145
|
-
|
|
146
|
-
const content = withIcon("sandboxRuns", `${sandboxCount}`);
|
|
147
|
-
return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function renderSearchQueriesSegment(ctx: FooterSegmentContext): RenderedSegment {
|
|
151
|
-
// Count search events from session manager branch
|
|
152
|
-
const events = getSessionEvents(ctx);
|
|
153
|
-
let searchCount = 0;
|
|
154
|
-
for (const e of events) {
|
|
155
|
-
if (!e || typeof e !== "object") continue;
|
|
156
|
-
const name = String((e as any).name ?? "").toLowerCase();
|
|
157
|
-
if (name.includes("search") || name.includes("ctx_search")) {
|
|
158
|
-
searchCount++;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (searchCount === 0) return hidden();
|
|
162
|
-
|
|
163
|
-
const content = withIcon("searchQueries", `${searchCount}`);
|
|
164
|
-
return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
|
|
65
|
+
const s = compactionSummary(branchOf(ctx));
|
|
66
|
+
if (s.count === 0) return { content: "", visible: false };
|
|
67
|
+
const dim = (t: string) => `\x1b[2m${t}\x1b[22m`;
|
|
68
|
+
const sizes = s.before > 0 ? ` ${formatTokens(s.before)}→${formatTokens(s.after)}` : "";
|
|
69
|
+
const age = s.lastAt != null ? dim(` · ${ago(Date.now() - s.lastAt)}`) : "";
|
|
70
|
+
const content = `${dim("cmp")} ${applyColor("compactor", `${s.count}×${sizes}`, ctx.theme, ctx.colors)}${age}`;
|
|
71
|
+
return { content, visible: true };
|
|
165
72
|
}
|
|
166
73
|
|
|
167
74
|
export const COMPACTOR_SEGMENTS: FooterSegment[] = [
|
|
168
|
-
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
75
|
+
{
|
|
76
|
+
id: "compactions",
|
|
77
|
+
label: "Compactions",
|
|
78
|
+
shortLabel: "CMP",
|
|
79
|
+
description: "Compactions this session: count, tokens before → after, time since the last (hidden until the first)",
|
|
80
|
+
zone: "center",
|
|
81
|
+
render: renderCompactionsSegment,
|
|
82
|
+
defaultShow: true,
|
|
83
|
+
},
|
|
174
84
|
];
|
|
@@ -23,7 +23,6 @@ const STATUS_DISPLAY: Record<string, { short: string; segmentId: string }> = {
|
|
|
23
23
|
workflow: { short: "WF", segmentId: "currentCommand" },
|
|
24
24
|
ralph: { short: "RL", segmentId: "activeLoops" },
|
|
25
25
|
memory: { short: "MEM", segmentId: "projectCount" },
|
|
26
|
-
compactor: { short: "CMP", segmentId: "compactions" },
|
|
27
26
|
mcp: { short: "MCP", segmentId: "serversTotal" },
|
|
28
27
|
notify: { short: "NTF", segmentId: "platformsEnabled" },
|
|
29
28
|
kanboard: { short: "KB", segmentId: "docsCount" },
|