@vtstech/pi-status 1.1.1 → 1.1.3
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/package.json +2 -2
- package/status.js +104 -184
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vtstech/pi-status",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "System monitor / status bar extension for Pi Coding Agent",
|
|
5
5
|
"main": "status.js",
|
|
6
6
|
"keywords": ["pi-extensions"],
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"url": "https://github.com/VTSTech/pi-coding-agent"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@vtstech/pi-shared": "1.1.
|
|
17
|
+
"@vtstech/pi-shared": "1.1.3"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@mariozechner/pi-coding-agent": ">=0.66"
|
package/status.js
CHANGED
|
@@ -1,33 +1,31 @@
|
|
|
1
1
|
// .build-npm/status/status.temp.ts
|
|
2
|
+
import * as fs from "node:fs";
|
|
2
3
|
import os from "node:os";
|
|
3
|
-
import { execSync as gitExecSync } from "node:child_process";
|
|
4
4
|
import { getOllamaBaseUrl, fetchModelContextLength, readModelsJson } from "@vtstech/pi-shared/ollama";
|
|
5
5
|
import { fmtBytes, fmtDur } from "@vtstech/pi-shared/format";
|
|
6
|
+
import { debugLog } from "@vtstech/pi-shared/debug";
|
|
7
|
+
var STATUS_UPDATE_INTERVAL_MS = 5e3;
|
|
8
|
+
var TOOL_TIMER_INTERVAL_MS = 1e3;
|
|
6
9
|
function status_temp_default(pi) {
|
|
7
10
|
let lastResponseTime = null;
|
|
8
11
|
let agentStartTime = null;
|
|
9
12
|
let updateInterval = null;
|
|
13
|
+
let toolTimerInterval = null;
|
|
10
14
|
let currentCtx = null;
|
|
11
15
|
let ctxUi = null;
|
|
12
16
|
let prevCpuInfo = getCpuSnapshot();
|
|
13
17
|
let lastPayload = null;
|
|
14
|
-
let tuiRef = null;
|
|
15
|
-
let gitBranchCache = "";
|
|
16
18
|
let cpuUsage = 0;
|
|
17
19
|
let memUsed = 0;
|
|
18
20
|
let memTotal = 0;
|
|
19
21
|
let swapUsed = 0;
|
|
20
22
|
let swapTotal = 0;
|
|
21
23
|
let hasSwap = false;
|
|
22
|
-
let ollamaLoaded = "";
|
|
23
24
|
let footerModel = "";
|
|
24
25
|
let footerThinking = "";
|
|
25
|
-
let footerCtxPct = "";
|
|
26
26
|
let footerNativeCtx = "";
|
|
27
27
|
let nativeCtxModel = "";
|
|
28
28
|
let isLocalProvider = true;
|
|
29
|
-
let lastUpstream = 0;
|
|
30
|
-
let lastDownstream = 0;
|
|
31
29
|
let securityFlashTool = "";
|
|
32
30
|
let securityFlashUntil = 0;
|
|
33
31
|
let activeTool = "";
|
|
@@ -65,18 +63,20 @@ function status_temp_default(pi) {
|
|
|
65
63
|
return { used, total };
|
|
66
64
|
}
|
|
67
65
|
function getSwap() {
|
|
66
|
+
if (process.platform !== "linux") {
|
|
67
|
+
debugLog("status", "swap detection skipped: not a Linux platform");
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
68
70
|
try {
|
|
69
|
-
const out =
|
|
71
|
+
const out = fs.readFileSync("/proc/meminfo", "utf-8");
|
|
70
72
|
const swapTotal2 = Number(out.match(/SwapTotal:\s+(\d+)/)?.[1]) * 1024;
|
|
71
73
|
const swapFree = Number(out.match(/SwapFree:\s+(\d+)/)?.[1]) * 1024;
|
|
72
74
|
if (swapTotal2 > 0) return { used: swapTotal2 - swapFree, total: swapTotal2 };
|
|
73
|
-
} catch {
|
|
75
|
+
} catch (err) {
|
|
76
|
+
debugLog("status", "failed to read /proc/meminfo", err);
|
|
74
77
|
}
|
|
75
78
|
return null;
|
|
76
79
|
}
|
|
77
|
-
let ollamaLoadedCache = "";
|
|
78
|
-
let ollamaLoadedLastCheck = 0;
|
|
79
|
-
const OLLAMA_LOADED_INTERVAL = 15e3;
|
|
80
80
|
function detectLocalProvider(modelsJson) {
|
|
81
81
|
const isLocalUrl = (url) => url.includes("localhost") || url.includes("127.0.0.1") || url.includes("0.0.0.0");
|
|
82
82
|
try {
|
|
@@ -116,33 +116,6 @@ function status_temp_default(pi) {
|
|
|
116
116
|
}
|
|
117
117
|
return footerNativeCtx;
|
|
118
118
|
}
|
|
119
|
-
async function fetchOllamaLoadedModel() {
|
|
120
|
-
try {
|
|
121
|
-
const ollamaBase = getOllamaBaseUrl();
|
|
122
|
-
const res = await fetch(`${ollamaBase}/api/ps`, {
|
|
123
|
-
signal: AbortSignal.timeout(5e3)
|
|
124
|
-
});
|
|
125
|
-
if (!res.ok) return "";
|
|
126
|
-
const data = await res.json();
|
|
127
|
-
const models = data?.models || [];
|
|
128
|
-
if (Array.isArray(models) && models.length > 0) {
|
|
129
|
-
return models[0].name || models[0].model || "";
|
|
130
|
-
}
|
|
131
|
-
} catch {
|
|
132
|
-
}
|
|
133
|
-
return "";
|
|
134
|
-
}
|
|
135
|
-
function getOllamaLoadedModel() {
|
|
136
|
-
const now = Date.now();
|
|
137
|
-
if (now - ollamaLoadedLastCheck < OLLAMA_LOADED_INTERVAL) return ollamaLoadedCache;
|
|
138
|
-
ollamaLoadedLastCheck = now;
|
|
139
|
-
fetchOllamaLoadedModel().then((loaded) => {
|
|
140
|
-
ollamaLoadedCache = loaded;
|
|
141
|
-
}).catch(() => {
|
|
142
|
-
ollamaLoadedCache = "";
|
|
143
|
-
});
|
|
144
|
-
return ollamaLoadedCache;
|
|
145
|
-
}
|
|
146
119
|
function extractParams(payload) {
|
|
147
120
|
const params = [];
|
|
148
121
|
if (payload.temperature !== void 0) params.push(`temp:${payload.temperature}`);
|
|
@@ -155,29 +128,46 @@ function status_temp_default(pi) {
|
|
|
155
128
|
if (payload.reasoning_effort !== void 0) params.push(`think:${payload.reasoning_effort}`);
|
|
156
129
|
return params;
|
|
157
130
|
}
|
|
158
|
-
function
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
131
|
+
function flushStatus() {
|
|
132
|
+
if (!ctxUi) return;
|
|
133
|
+
ctxUi.setStatus("status-cpu", isLocalProvider ? `CPU ${cpuUsage.toFixed(0)}%` : void 0);
|
|
134
|
+
ctxUi.setStatus("status-ram", isLocalProvider ? `RAM ${fmtBytes(memUsed)}/${fmtBytes(memTotal)}` : void 0);
|
|
135
|
+
ctxUi.setStatus(
|
|
136
|
+
"status-swap",
|
|
137
|
+
isLocalProvider && hasSwap && swapUsed > 0 ? `Swap ${fmtBytes(swapUsed)}/${fmtBytes(swapTotal)}` : void 0
|
|
138
|
+
);
|
|
139
|
+
ctxUi.setStatus(
|
|
140
|
+
"status-native-ctx",
|
|
141
|
+
isLocalProvider && footerNativeCtx ? `M:${footerNativeCtx}` : void 0
|
|
142
|
+
);
|
|
143
|
+
ctxUi.setStatus(
|
|
144
|
+
"status-thinking",
|
|
145
|
+
footerThinking && footerThinking !== "off" ? footerThinking : void 0
|
|
146
|
+
);
|
|
147
|
+
ctxUi.setStatus(
|
|
148
|
+
"status-resp",
|
|
149
|
+
lastResponseTime !== null ? `Resp ${fmtDur(lastResponseTime)}` : void 0
|
|
150
|
+
);
|
|
151
|
+
if (lastPayload) {
|
|
152
|
+
const params = extractParams(lastPayload);
|
|
153
|
+
ctxUi.setStatus("status-params", params.length > 0 ? params.join(" ") : void 0);
|
|
154
|
+
} else {
|
|
155
|
+
ctxUi.setStatus("status-params", void 0);
|
|
156
|
+
}
|
|
157
|
+
const now = Date.now();
|
|
158
|
+
if (securityFlashTool && now < securityFlashUntil) {
|
|
159
|
+
ctxUi.setStatus("status-sec", `SEC:${blockedCount} (blocked: ${securityFlashTool})`);
|
|
160
|
+
} else if (blockedCount > 0) {
|
|
161
|
+
ctxUi.setStatus("status-sec", `SEC:${blockedCount}`);
|
|
162
|
+
} else {
|
|
163
|
+
ctxUi.setStatus("status-sec", void 0);
|
|
164
|
+
}
|
|
165
|
+
if (activeTool && activeToolStart > 0) {
|
|
166
|
+
const elapsed = performance.now() - activeToolStart;
|
|
167
|
+
ctxUi.setStatus("status-tool", `> ${activeTool}: ${fmtDur(elapsed)}`);
|
|
168
|
+
} else {
|
|
169
|
+
ctxUi.setStatus("status-tool", void 0);
|
|
176
170
|
}
|
|
177
|
-
return gitBranchCache;
|
|
178
|
-
}
|
|
179
|
-
function incrementBlockedCount() {
|
|
180
|
-
blockedCount++;
|
|
181
171
|
}
|
|
182
172
|
function updateMetrics() {
|
|
183
173
|
cpuUsage = getCpuUsage();
|
|
@@ -192,154 +182,73 @@ function status_temp_default(pi) {
|
|
|
192
182
|
} else {
|
|
193
183
|
hasSwap = false;
|
|
194
184
|
}
|
|
195
|
-
ollamaLoaded = getOllamaLoadedModel();
|
|
196
185
|
const modelsJson = readModelsJson();
|
|
197
186
|
isLocalProvider = modelsJson ? detectLocalProvider(modelsJson) : false;
|
|
198
187
|
if (currentCtx) {
|
|
199
188
|
footerModel = currentCtx.model?.id || "";
|
|
200
189
|
footerThinking = pi.getThinkingLevel?.() ?? "";
|
|
201
|
-
const usage = currentCtx.getContextUsage?.();
|
|
202
|
-
if (usage && usage.contextWindow > 0) {
|
|
203
|
-
const pctVal = (usage.tokens / usage.contextWindow * 100).toFixed(1);
|
|
204
|
-
footerCtxPct = `${pctVal}%/${(usage.contextWindow / 1e3).toFixed(0)}k`;
|
|
205
|
-
} else {
|
|
206
|
-
footerCtxPct = "";
|
|
207
|
-
}
|
|
208
190
|
const modelId = currentCtx.model?.id || "";
|
|
209
191
|
if (modelId && isLocalProvider) {
|
|
210
192
|
getNativeModelCtx(modelId);
|
|
211
193
|
}
|
|
212
194
|
}
|
|
195
|
+
flushStatus();
|
|
213
196
|
}
|
|
214
197
|
pi.on("session_start", async (_event, ctx) => {
|
|
215
198
|
currentCtx = ctx;
|
|
216
199
|
ctxUi = ctx.ui;
|
|
217
200
|
prevCpuInfo = getCpuSnapshot();
|
|
218
201
|
updateMetrics();
|
|
219
|
-
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
220
|
-
tuiRef = tui;
|
|
221
|
-
const dim = (s) => theme?.fg?.("dim", s) ?? s;
|
|
222
|
-
const red = (s) => theme?.fg?.("error", s) ?? s;
|
|
223
|
-
const yellow = (s) => theme?.fg?.("yellow", s) ?? s;
|
|
224
|
-
const sep = dim(" \xB7 ");
|
|
225
|
-
const truncateLine = (line, maxW) => {
|
|
226
|
-
const ellipsis = dim("...");
|
|
227
|
-
const visible = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
228
|
-
if (visible.length > maxW) {
|
|
229
|
-
let vis = 0, cut = 0;
|
|
230
|
-
for (let i = 0; i < line.length && vis < maxW - 3; i++) {
|
|
231
|
-
if (line[i] === "\x1B") {
|
|
232
|
-
while (i < line.length && line[i] !== "m") i++;
|
|
233
|
-
} else {
|
|
234
|
-
vis++;
|
|
235
|
-
}
|
|
236
|
-
cut = i + 1;
|
|
237
|
-
}
|
|
238
|
-
return line.slice(0, cut) + ellipsis;
|
|
239
|
-
}
|
|
240
|
-
return line;
|
|
241
|
-
};
|
|
242
|
-
return {
|
|
243
|
-
render(width) {
|
|
244
|
-
const lines = [];
|
|
245
|
-
let branch = "";
|
|
246
|
-
try {
|
|
247
|
-
branch = footerData?.getGitBranch?.() || "";
|
|
248
|
-
} catch {
|
|
249
|
-
}
|
|
250
|
-
if (!branch) branch = getGitBranch();
|
|
251
|
-
const line1Parts = [];
|
|
252
|
-
if (footerModel) line1Parts.push(`conf:${footerModel}`);
|
|
253
|
-
line1Parts.push(getPwd());
|
|
254
|
-
if (footerThinking && footerThinking !== "off") line1Parts.push(dim(footerThinking));
|
|
255
|
-
if (isLocalProvider) {
|
|
256
|
-
line1Parts.push(dim(`CPU ${cpuUsage.toFixed(0)}%`));
|
|
257
|
-
}
|
|
258
|
-
let line1 = truncateLine(line1Parts.join(sep), width);
|
|
259
|
-
lines.push(line1);
|
|
260
|
-
const line2Parts = [];
|
|
261
|
-
if (ollamaLoaded) line2Parts.push(`load:${ollamaLoaded}`);
|
|
262
|
-
if (footerNativeCtx) line2Parts.push(`M:${footerNativeCtx}`);
|
|
263
|
-
if (footerCtxPct) line2Parts.push(`S:${footerCtxPct}`);
|
|
264
|
-
if (isLocalProvider) {
|
|
265
|
-
line2Parts.push(`RAM ${fmtBytes(memUsed)}/${fmtBytes(memTotal)}`);
|
|
266
|
-
if (hasSwap && swapUsed > 0) {
|
|
267
|
-
line2Parts.push(`Swap ${fmtBytes(swapUsed)}/${fmtBytes(swapTotal)}`);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
if (lastUpstream > 0 || lastDownstream > 0) {
|
|
271
|
-
line2Parts.push(dim(`\u2191${fmtTk(lastUpstream)} \u2193${fmtTk(lastDownstream)}`));
|
|
272
|
-
}
|
|
273
|
-
if (lastResponseTime !== null) line2Parts.push(`Resp ${fmtDur(lastResponseTime)}`);
|
|
274
|
-
if (lastPayload) {
|
|
275
|
-
const params = extractParams(lastPayload);
|
|
276
|
-
if (params.length > 0) line2Parts.push(...params.map((p) => dim(p)));
|
|
277
|
-
}
|
|
278
|
-
const now = Date.now();
|
|
279
|
-
if (securityFlashTool && now < securityFlashUntil) {
|
|
280
|
-
line2Parts.push(red(`BLOCKED:${securityFlashTool}`));
|
|
281
|
-
}
|
|
282
|
-
if (blockedCount > 0) {
|
|
283
|
-
line2Parts.push(red(`SEC:${blockedCount}`));
|
|
284
|
-
}
|
|
285
|
-
let line2 = truncateLine(line2Parts.join(sep), width);
|
|
286
|
-
if (line2) lines.push(line2);
|
|
287
|
-
if (activeTool && activeToolStart > 0) {
|
|
288
|
-
const elapsed = performance.now() - activeToolStart;
|
|
289
|
-
lines.push(`${yellow("\u23F3")} ${activeTool}: ${fmtDur(elapsed)}`);
|
|
290
|
-
}
|
|
291
|
-
return lines;
|
|
292
|
-
},
|
|
293
|
-
invalidate() {
|
|
294
|
-
},
|
|
295
|
-
dispose() {
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
});
|
|
299
202
|
if (updateInterval) clearInterval(updateInterval);
|
|
300
|
-
updateInterval = setInterval(
|
|
301
|
-
updateMetrics();
|
|
302
|
-
if (tuiRef) tuiRef.requestRender();
|
|
303
|
-
}, 3e3);
|
|
203
|
+
updateInterval = setInterval(updateMetrics, STATUS_UPDATE_INTERVAL_MS);
|
|
304
204
|
});
|
|
305
|
-
pi.on("session_shutdown", async () => {
|
|
306
|
-
if (updateInterval)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
205
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
206
|
+
if (updateInterval) {
|
|
207
|
+
clearInterval(updateInterval);
|
|
208
|
+
updateInterval = null;
|
|
209
|
+
}
|
|
210
|
+
if (toolTimerInterval) {
|
|
211
|
+
clearInterval(toolTimerInterval);
|
|
212
|
+
toolTimerInterval = null;
|
|
312
213
|
}
|
|
214
|
+
ctxUi = null;
|
|
313
215
|
currentCtx = null;
|
|
216
|
+
const ui = ctx?.ui;
|
|
217
|
+
if (ui) {
|
|
218
|
+
ui.setStatus("status-cpu", void 0);
|
|
219
|
+
ui.setStatus("status-ram", void 0);
|
|
220
|
+
ui.setStatus("status-swap", void 0);
|
|
221
|
+
ui.setStatus("status-native-ctx", void 0);
|
|
222
|
+
ui.setStatus("status-thinking", void 0);
|
|
223
|
+
ui.setStatus("status-resp", void 0);
|
|
224
|
+
ui.setStatus("status-params", void 0);
|
|
225
|
+
ui.setStatus("system-prompt", void 0);
|
|
226
|
+
ui.setStatus("status-sec", void 0);
|
|
227
|
+
ui.setStatus("status-tool", void 0);
|
|
228
|
+
}
|
|
314
229
|
securityFlashTool = "";
|
|
315
230
|
securityFlashUntil = 0;
|
|
316
231
|
activeTool = "";
|
|
317
232
|
activeToolStart = 0;
|
|
318
233
|
blockedCount = 0;
|
|
319
|
-
|
|
320
|
-
|
|
234
|
+
lastResponseTime = null;
|
|
235
|
+
lastPayload = null;
|
|
321
236
|
});
|
|
322
237
|
pi.on("before_provider_request", (event) => {
|
|
323
238
|
lastPayload = event.payload;
|
|
324
239
|
});
|
|
325
|
-
|
|
326
|
-
if (event?.message?.role !== "assistant") return;
|
|
327
|
-
const usage = event?.message?.usage ?? // normalised Pi usage
|
|
328
|
-
event?.usage ?? // alternative path
|
|
329
|
-
null;
|
|
330
|
-
if (!usage) return;
|
|
331
|
-
const inp = usage.input ?? usage.promptTokens ?? usage.prompt_tokens;
|
|
332
|
-
const out = usage.output ?? usage.completionTokens ?? usage.completion_tokens;
|
|
333
|
-
if (inp != null) lastUpstream = inp;
|
|
334
|
-
if (out != null) lastDownstream = out;
|
|
335
|
-
if (tuiRef) tuiRef.requestRender();
|
|
336
|
-
}
|
|
337
|
-
pi.on("message_end", captureUsage);
|
|
338
|
-
pi.on("turn_end", captureUsage);
|
|
339
|
-
pi.on("agent_start", async () => {
|
|
240
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
340
241
|
agentStartTime = performance.now();
|
|
341
|
-
|
|
342
|
-
|
|
242
|
+
try {
|
|
243
|
+
const prompt = ctx.getSystemPrompt();
|
|
244
|
+
const theme = ctx.ui.theme;
|
|
245
|
+
const chr = prompt.length;
|
|
246
|
+
const tok = prompt.split(/\s+/).filter(Boolean).length;
|
|
247
|
+
const label = theme.fg("dim", "Prompt:");
|
|
248
|
+
const value = theme.fg("success", `${chr} chr ${tok} tok`);
|
|
249
|
+
ctxUi?.setStatus("system-prompt", `${label} ${value}`);
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
343
252
|
});
|
|
344
253
|
pi.on("agent_end", async () => {
|
|
345
254
|
if (agentStartTime !== null) {
|
|
@@ -348,29 +257,40 @@ function status_temp_default(pi) {
|
|
|
348
257
|
}
|
|
349
258
|
activeTool = "";
|
|
350
259
|
activeToolStart = 0;
|
|
260
|
+
stopToolTimer();
|
|
351
261
|
updateMetrics();
|
|
352
|
-
if (tuiRef) tuiRef.requestRender();
|
|
353
262
|
});
|
|
263
|
+
function startToolTimer() {
|
|
264
|
+
if (toolTimerInterval) return;
|
|
265
|
+
toolTimerInterval = setInterval(flushStatus, TOOL_TIMER_INTERVAL_MS);
|
|
266
|
+
}
|
|
267
|
+
function stopToolTimer() {
|
|
268
|
+
if (toolTimerInterval) {
|
|
269
|
+
clearInterval(toolTimerInterval);
|
|
270
|
+
toolTimerInterval = null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
354
273
|
pi.on("tool_call", (event) => {
|
|
355
274
|
if (!event) return;
|
|
356
275
|
const isBlocked = event.blocked === true || event.blocked === "true" || event.result?.blocked === true || event.error?.includes("blocked");
|
|
357
276
|
if (isBlocked) {
|
|
358
277
|
securityFlashTool = event.tool ?? event.name ?? "unknown";
|
|
359
278
|
securityFlashUntil = Date.now() + 3e3;
|
|
360
|
-
|
|
361
|
-
|
|
279
|
+
blockedCount++;
|
|
280
|
+
flushStatus();
|
|
362
281
|
}
|
|
363
282
|
});
|
|
364
283
|
pi.on("tool_execution_start", (event) => {
|
|
365
284
|
if (!event) return;
|
|
366
285
|
activeTool = event.tool ?? event.name ?? "tool";
|
|
367
286
|
activeToolStart = performance.now();
|
|
368
|
-
|
|
287
|
+
startToolTimer();
|
|
369
288
|
});
|
|
370
289
|
pi.on("tool_execution_end", () => {
|
|
371
290
|
activeTool = "";
|
|
372
291
|
activeToolStart = 0;
|
|
373
|
-
|
|
292
|
+
stopToolTimer();
|
|
293
|
+
flushStatus();
|
|
374
294
|
});
|
|
375
295
|
}
|
|
376
296
|
export {
|