@vtstech/pi-status 1.1.2 → 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.
Files changed (2) hide show
  1. package/package.json +2 -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.2",
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.2"
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 = gitExecSync("cat /proc/meminfo", { encoding: "utf-8", timeout: 3e3 });
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 fmtTk(n) {
159
- if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
160
- return String(n);
161
- }
162
- function getPwd() {
163
- const cwd = process.cwd();
164
- if (cwd.startsWith(os.homedir())) return "~" + cwd.slice(os.homedir().length);
165
- return cwd;
166
- }
167
- function getGitBranch() {
168
- if (gitBranchCache) return gitBranchCache;
169
- try {
170
- const branch = gitExecSync("git rev-parse --abbrev-ref HEAD 2>/dev/null", {
171
- encoding: "utf-8",
172
- timeout: 3e3
173
- }).trim();
174
- if (branch) gitBranchCache = branch;
175
- } catch {
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) clearInterval(updateInterval);
307
- updateInterval = null;
308
- tuiRef = null;
309
- if (ctxUi) {
310
- ctxUi.setFooter(void 0);
311
- ctxUi = null;
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
- lastUpstream = 0;
320
- lastDownstream = 0;
234
+ lastResponseTime = null;
235
+ lastPayload = null;
321
236
  });
322
237
  pi.on("before_provider_request", (event) => {
323
238
  lastPayload = event.payload;
324
239
  });
325
- function captureUsage(event) {
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
- lastUpstream = 0;
342
- lastDownstream = 0;
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
- incrementBlockedCount();
361
- if (tuiRef) tuiRef.requestRender();
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
- if (tuiRef) tuiRef.requestRender();
287
+ startToolTimer();
369
288
  });
370
289
  pi.on("tool_execution_end", () => {
371
290
  activeTool = "";
372
291
  activeToolStart = 0;
373
- if (tuiRef) tuiRef.requestRender();
292
+ stopToolTimer();
293
+ flushStatus();
374
294
  });
375
295
  }
376
296
  export {