@mrclrchtr/supi-cache 1.12.1 → 1.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/node_modules/@mrclrchtr/supi-core/package.json +2 -1
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +57 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +8 -1
- package/package.json +2 -2
- package/src/monitor/monitor.ts +23 -8
- package/src/monitor/status.ts +8 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"./config": "./src/config.ts",
|
|
45
45
|
"./context": "./src/context.ts",
|
|
46
46
|
"./debug": "./src/debug-registry.ts",
|
|
47
|
+
"./footer-registry": "./src/footer-registry.ts",
|
|
47
48
|
"./llm": "./src/llm.ts",
|
|
48
49
|
"./package.json": "./package.json",
|
|
49
50
|
"./path": "./src/path.ts",
|
|
@@ -13,6 +13,8 @@ export * from "./context.ts";
|
|
|
13
13
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
14
14
|
export * from "./debug-registry.ts";
|
|
15
15
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
16
|
+
export * from "./footer-registry.ts";
|
|
17
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
16
18
|
export * from "./llm.ts";
|
|
17
19
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
18
20
|
export * from "./path.ts";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Shared footer contribution registry for SuPi extensions.
|
|
2
|
+
//
|
|
3
|
+
// Extensions register pre-styled text chunks with a placement hint
|
|
4
|
+
// ("stats" for the metrics line, "status" for the extension status line).
|
|
5
|
+
// The custom footer in supi-extras (or PI's built-in footer) reads these
|
|
6
|
+
// contributions and renders them alongside the built-in metrics.
|
|
7
|
+
|
|
8
|
+
import { createRegistry } from "./registry-utils.ts";
|
|
9
|
+
|
|
10
|
+
/** Where the contribution should appear in the footer. */
|
|
11
|
+
export type FooterPlacement = "stats" | "status";
|
|
12
|
+
|
|
13
|
+
/** A single footer contribution registered by an extension. */
|
|
14
|
+
export interface FooterContribution {
|
|
15
|
+
/** Unique key for this contribution. Re-registering with the same key replaces it. */
|
|
16
|
+
key: string;
|
|
17
|
+
/** Which footer line this belongs on. */
|
|
18
|
+
placement: FooterPlacement;
|
|
19
|
+
/**
|
|
20
|
+
* Sort order within the placement (lower values render further left). Default: 100.
|
|
21
|
+
* Priority 0 is reserved for the turn cache-hit part so it stays adjacent to CH.
|
|
22
|
+
*/
|
|
23
|
+
priority?: number;
|
|
24
|
+
/** Return the pre-styled text for this contribution. Called on every render. */
|
|
25
|
+
render: () => string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const registry = createRegistry<FooterContribution>("footer-contributions");
|
|
29
|
+
|
|
30
|
+
function sortByPriority(a: FooterContribution, b: FooterContribution): number {
|
|
31
|
+
return (a.priority ?? 100) - (b.priority ?? 100);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const footerContributions = {
|
|
35
|
+
/** Register or replace a footer contribution. */
|
|
36
|
+
register(contribution: FooterContribution): void {
|
|
37
|
+
registry.register(contribution.key, contribution);
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/** Remove a contribution (e.g. on session_shutdown or when disabled). */
|
|
41
|
+
unregister(key: string): void {
|
|
42
|
+
registry.unregister(key);
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/** Get contributions for a specific placement, sorted by priority. */
|
|
46
|
+
getByPlacement(placement: FooterPlacement): FooterContribution[] {
|
|
47
|
+
return registry
|
|
48
|
+
.getAll()
|
|
49
|
+
.filter((c) => c.placement === placement)
|
|
50
|
+
.sort(sortByPriority);
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/** Remove all contributions (primarily for tests). */
|
|
54
|
+
clear(): void {
|
|
55
|
+
registry.clear();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
@@ -13,6 +13,8 @@ export * from "./context.ts";
|
|
|
13
13
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
14
14
|
export * from "./debug-registry.ts";
|
|
15
15
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
16
|
+
export * from "./footer-registry.ts";
|
|
17
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
16
18
|
export * from "./path.ts";
|
|
17
19
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
18
20
|
export * from "./project.ts";
|
|
@@ -27,7 +27,7 @@ function getGlobalRegistryMap<T>(name: string): Map<string, T> {
|
|
|
27
27
|
*
|
|
28
28
|
* @typeParam T - The value type stored in the registry.
|
|
29
29
|
* @param name - Unique registry name (used to construct the `Symbol.for` key).
|
|
30
|
-
* @returns An object with `register`, `getAll`, and `clear` functions.
|
|
30
|
+
* @returns An object with `register`, `unregister`, `getAll`, and `clear` functions.
|
|
31
31
|
*/
|
|
32
32
|
export function createRegistry<T>(name: string) {
|
|
33
33
|
const getMap = (): Map<string, T> => getGlobalRegistryMap<T>(name);
|
|
@@ -40,6 +40,13 @@ export function createRegistry<T>(name: string) {
|
|
|
40
40
|
getMap().set(id, value);
|
|
41
41
|
},
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Remove a registration by id. No-op if not registered.
|
|
45
|
+
*/
|
|
46
|
+
unregister: (id: string): void => {
|
|
47
|
+
getMap().delete(id);
|
|
48
|
+
},
|
|
49
|
+
|
|
43
50
|
/**
|
|
44
51
|
* Get all registered values in registration order.
|
|
45
52
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-cache",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "SuPi Cache — prompt cache health monitoring and cross-session forensics",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@mrclrchtr/supi-core": "1.
|
|
23
|
+
"@mrclrchtr/supi-core": "1.13.0"
|
|
24
24
|
},
|
|
25
25
|
"bundledDependencies": [
|
|
26
26
|
"@mrclrchtr/supi-core"
|
package/src/monitor/monitor.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
7
7
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
9
|
+
import { footerContributions } from "@mrclrchtr/supi-core/footer-registry";
|
|
9
10
|
import { Type } from "typebox";
|
|
10
11
|
import { loadCacheMonitorConfig } from "../config.ts";
|
|
11
12
|
import { computePromptFingerprint, diffFingerprints, zeroFingerprint } from "../fingerprint.ts";
|
|
@@ -44,6 +45,22 @@ export default function cacheMonitorExtension(pi: ExtensionAPI) {
|
|
|
44
45
|
return loadCacheMonitorConfig(ctx.cwd).regressionThreshold;
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
// ── Helpers ──────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
function updateFooterStatus(): void {
|
|
51
|
+
const statusText = formatCacheStatus(state);
|
|
52
|
+
if (statusText) {
|
|
53
|
+
footerContributions.register({
|
|
54
|
+
key: STATUS_KEY,
|
|
55
|
+
placement: "stats",
|
|
56
|
+
priority: 0,
|
|
57
|
+
render: () => statusText,
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
footerContributions.unregister(STATUS_KEY);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
47
64
|
// ── message_end: record turn + update status + check regression
|
|
48
65
|
|
|
49
66
|
pi.on("message_end", async (event, ctx) => {
|
|
@@ -59,9 +76,8 @@ export default function cacheMonitorExtension(pi: ExtensionAPI) {
|
|
|
59
76
|
// Persist turn record
|
|
60
77
|
pi.appendEntry(ENTRY_TYPE, record);
|
|
61
78
|
|
|
62
|
-
// Update footer
|
|
63
|
-
|
|
64
|
-
ctx.ui.setStatus(STATUS_KEY, statusText);
|
|
79
|
+
// Update footer contribution
|
|
80
|
+
updateFooterStatus();
|
|
65
81
|
|
|
66
82
|
// Check regression
|
|
67
83
|
const regression = state.detectRegression(getThreshold(ctx));
|
|
@@ -106,22 +122,21 @@ export default function cacheMonitorExtension(pi: ExtensionAPI) {
|
|
|
106
122
|
state.reset();
|
|
107
123
|
|
|
108
124
|
if (!isEnabled(ctx)) {
|
|
109
|
-
|
|
125
|
+
footerContributions.unregister(STATUS_KEY);
|
|
110
126
|
return;
|
|
111
127
|
}
|
|
112
128
|
|
|
113
129
|
const branch = ctx.sessionManager.getBranch();
|
|
114
130
|
state.restoreFromEntries(branch);
|
|
115
131
|
|
|
116
|
-
|
|
117
|
-
ctx.ui.setStatus(STATUS_KEY, statusText);
|
|
132
|
+
updateFooterStatus();
|
|
118
133
|
});
|
|
119
134
|
|
|
120
135
|
// ── session_shutdown: clear state ─────────────────────────
|
|
121
136
|
|
|
122
|
-
pi.on("session_shutdown", async (_event,
|
|
137
|
+
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
123
138
|
state.reset();
|
|
124
|
-
|
|
139
|
+
footerContributions.unregister(STATUS_KEY);
|
|
125
140
|
});
|
|
126
141
|
|
|
127
142
|
// ── /supi-cache-history command ──────────────────────────
|
package/src/monitor/status.ts
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
import type { CacheMonitorState } from "./state.ts";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Format the compact footer status string.
|
|
6
|
+
* Format the compact footer status string for the stats line.
|
|
7
7
|
*
|
|
8
|
-
* - `
|
|
9
|
-
* - `
|
|
10
|
-
* - `
|
|
8
|
+
* - `TCH87%↑` — hit rate with trend arrow
|
|
9
|
+
* - `TCH100%` — first turn (no trend)
|
|
10
|
+
* - `undefined` — no cache data available (contribution suppressed)
|
|
11
11
|
*/
|
|
12
12
|
export function formatCacheStatus(state: CacheMonitorState): string | undefined {
|
|
13
13
|
const latest = state.getLatestTurn();
|
|
@@ -15,7 +15,7 @@ export function formatCacheStatus(state: CacheMonitorState): string | undefined
|
|
|
15
15
|
|
|
16
16
|
// No cache data: unsupported provider or zero denominator
|
|
17
17
|
if (!state.cacheSupported || latest.hitRate === undefined) {
|
|
18
|
-
return
|
|
18
|
+
return undefined;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const previous = state.getPreviousTurn();
|
|
@@ -23,11 +23,11 @@ export function formatCacheStatus(state: CacheMonitorState): string | undefined
|
|
|
23
23
|
|
|
24
24
|
if (previous?.hitRate !== undefined) {
|
|
25
25
|
if (latest.hitRate > previous.hitRate) {
|
|
26
|
-
trend = "
|
|
26
|
+
trend = "↑";
|
|
27
27
|
} else if (latest.hitRate < previous.hitRate) {
|
|
28
|
-
trend = "
|
|
28
|
+
trend = "↓";
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
return `
|
|
32
|
+
return `TCH${latest.hitRate}%${trend}`;
|
|
33
33
|
}
|