pi-metrics 0.2.2 → 0.3.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 CHANGED
@@ -11,7 +11,7 @@ Session metrics for the [Pi coding agent](https://github.com/earendil-works/pi):
11
11
  - When the agent fully settles (`agent_settled` — including auto-retries, compaction continuations, or Esc interruption), a final line shows the **total elapsed time from message send to stop** (`⏱ Total elapsed 18.9s`).
12
12
  - After each LLM turn, a notification reports TPS, TTFT, token counts, generation time, stalls, and blended cost when available.
13
13
  - Telemetry is persisted as `tps` custom session entries and restored after session resume or `/tree` navigation.
14
- - `/tps-export` exports telemetry JSONL; `/session-export` exports the session JSONL.
14
+ - Metrics are exposed through session entries and notifications; this package does not register export slash commands.
15
15
 
16
16
  ## Migration from pi-tps
17
17
 
package/README.zh-CN.md CHANGED
@@ -11,7 +11,7 @@
11
11
  - AI 完全停止时(`agent_settled`,覆盖自动重试、compaction 续跑以及 Esc 中断)追加一行**从发出消息到停止的总耗时**(`⏱ 总耗时 18.9s`)。
12
12
  - 每轮 LLM 调用结束后显示 TPS、TTFT、token 数、生成耗时、stall 和可用的综合成本。
13
13
  - Telemetry 以 `tps` custom session entry 持久化,并在恢复 session 或 `/tree` 后恢复显示。
14
- - `/tps-export` 导出 TPS JSONL,`/session-export` 导出 session JSONL。
14
+ - Metrics 通过 session entry 和通知提供;本包不再注册导出类斜杠命令。
15
15
 
16
16
  ## 从 pi-tps 迁移
17
17
 
package/SKILL.md ADDED
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: configure-pi-metrics
3
+ description: "启用与排查 pi-metrics 的耗时、TPS、TTFT、token 和成本遥测。Use when configuring or diagnosing Pi session metrics."
4
+ ---
5
+
6
+ # 配置 pi-metrics
7
+
8
+ `pi-metrics` 当前没有配置文件或配置命令;不要编造开关、阈值或导出命令。配置动作仅限安装、启用或禁用该 package,以及共享语言设置。
9
+
10
+ ## 诊断与修改
11
+
12
+ 1. 确认 `npm:pi-metrics` 已安装且扩展资源已启用。
13
+ 2. 若同时启用了 `npm:@monotykamary/pi-tps`,先让用户选择保留一个;两者会重复写入 `tps` session entry 和通知。
14
+ 3. 文案语言由 `pi-extensions-i18n` 控制。
15
+
16
+ ## 验证
17
+
18
+ 在 TUI 中完成一次真实模型回合,观察 working spinner、轮次耗时、总耗时和可用的 TPS/TTFT/token/cost。RPC/print 模式不会启动 UI 定时器或通知;provider 未返回 usage 时部分指标不可用。真实模型回合属于 E2E,执行前遵守当前任务授权边界;未运行时明确报告 `NOT_RUN`。
@@ -38,33 +38,5 @@
38
38
  "tpsRate": {
39
39
  "zh-CN": "${value}/M",
40
40
  "en-US": "${value}/M"
41
- },
42
- "tpsExportDescription": {
43
- "zh-CN": "导出 TPS telemetry JSONL(--full 导出整个 session)",
44
- "en-US": "Export TPS telemetry as JSONL (--full for the full session)"
45
- },
46
- "sessionExportDescription": {
47
- "zh-CN": "导出 session JSONL(--full 导出所有分支)",
48
- "en-US": "Export session JSONL (--full for all branches)"
49
- },
50
- "tpsFullFlag": {
51
- "zh-CN": "--full(导出所有分支)",
52
- "en-US": "--full (all branches)"
53
- },
54
- "tpsNoEntries": {
55
- "zh-CN": "在 {scope} 中没有匹配的条目",
56
- "en-US": "No matching entries found in {scope}"
57
- },
58
- "tpsAllEntries": {
59
- "zh-CN": "全部条目",
60
- "en-US": "all entries"
61
- },
62
- "tpsCurrentBranch": {
63
- "zh-CN": "当前分支",
64
- "en-US": "current branch"
65
- },
66
- "tpsExported": {
67
- "zh-CN": "已导出 {count} 个条目 → {filepath}",
68
- "en-US": "Exported {count} entries → {filepath}"
69
41
  }
70
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-metrics",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Pi metrics extension: elapsed-time HUD and resilient TPS telemetry",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -12,7 +12,8 @@
12
12
  "src",
13
13
  "locales",
14
14
  "README.md",
15
- "README.zh-CN.md"
15
+ "README.zh-CN.md",
16
+ "SKILL.md"
16
17
  ],
17
18
  "scripts": {
18
19
  "test": "tsx --test tests/*.test.ts",
@@ -22,7 +23,11 @@
22
23
  },
23
24
  "pi": {
24
25
  "extensions": [
25
- "./index.ts"
26
+ "./index.ts",
27
+ "../pi-extensions-i18n/index.ts"
28
+ ],
29
+ "skills": [
30
+ "./SKILL.md"
26
31
  ]
27
32
  },
28
33
  "engines": {
@@ -54,8 +59,10 @@
54
59
  "peerDependencies": {
55
60
  "@earendil-works/pi-ai": ">=0.80.0 <0.81.0",
56
61
  "@earendil-works/pi-coding-agent": ">=0.80.0 <0.81.0",
57
- "@earendil-works/pi-tui": ">=0.80.0 <0.81.0",
58
- "pi-extensions-i18n": "^0.3.0"
62
+ "@earendil-works/pi-tui": ">=0.80.0 <0.81.0"
63
+ },
64
+ "dependencies": {
65
+ "pi-extensions-i18n": "^0.4.0"
59
66
  },
60
67
  "devDependencies": {
61
68
  "@earendil-works/pi-ai": "0.80.10",
package/src/tps.ts CHANGED
@@ -5,15 +5,10 @@
5
5
  * elapsed-time HUD and generation telemetry share one lifecycle.
6
6
  */
7
7
 
8
- import { execFileSync } from "node:child_process";
9
- import { mkdirSync, writeFileSync } from "node:fs";
10
- import { homedir } from "node:os";
11
- import { join } from "node:path";
12
8
  import { performance } from "node:perf_hooks";
13
9
  import type { AssistantMessage } from "@earendil-works/pi-ai";
14
10
  import type {
15
11
  ExtensionAPI,
16
- ExtensionCommandContext,
17
12
  ExtensionContext,
18
13
  } from "@earendil-works/pi-coding-agent";
19
14
  import { i18n } from "./i18n.ts";
@@ -345,54 +340,6 @@ function restoreTPSNotification(
345
340
  }
346
341
  }
347
342
 
348
- function openDirectory(directory: string): void {
349
- try {
350
- execFileSync(process.platform === "darwin" ? "open" : "xdg-open", [directory], {
351
- stdio: "ignore",
352
- });
353
- } catch {
354
- // Opening an export directory is optional; the path is still reported.
355
- }
356
- }
357
-
358
- function exportEntries(
359
- entries: SessionEntryLike[],
360
- full: boolean,
361
- filterType: string | null,
362
- directoryName: "pi-telemetry" | "pi-sessions",
363
- prefix: "pi-telemetry" | "pi-session",
364
- sessionId: string,
365
- ): { filepath: string; count: number } {
366
- const isStructural = (entry: SessionEntryLike) =>
367
- entry.type === "model_change" || entry.type === "branch_summary";
368
- const exported = entries.filter(
369
- (entry) => isStructural(entry) ||
370
- (entry.type === "custom" && (!filterType || entry.customType === filterType)),
371
- );
372
- const byId = new Map(entries.map((entry) => [entry.id, entry]));
373
- const exportedIds = new Set(exported.map((entry) => entry.id));
374
- const rechainParentId = (entry: SessionEntryLike): string | null => {
375
- let parentId = entry.parentId ?? null;
376
- while (parentId) {
377
- if (exportedIds.has(parentId)) return parentId;
378
- parentId = byId.get(parentId)?.parentId ?? null;
379
- }
380
- return null;
381
- };
382
- const rechained = exported.map((entry) => ({ ...entry, parentId: rechainParentId(entry) }));
383
- const cacheBase = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
384
- const directory = join(cacheBase, directoryName);
385
- mkdirSync(directory, { recursive: true });
386
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
387
- const scope = [full ? "full" : "branch", filterType?.replace(/[^a-zA-Z0-9_-]/g, "-")]
388
- .filter(Boolean)
389
- .join("-");
390
- const filepath = join(directory, `${prefix}-${scope}-${sessionId.slice(0, 8)}-${timestamp}.jsonl`);
391
- writeFileSync(filepath, `${rechained.map((entry) => JSON.stringify(entry)).join("\n")}\n`);
392
- openDirectory(directory);
393
- return { filepath, count: exported.length };
394
- }
395
-
396
343
  export default function tpsExtension(pi: ExtensionAPI): void {
397
344
  let currentTiming: TurnTiming | null = null;
398
345
  let pendingNeuralwattBilledCost: { turnIndex: number; costUsd: number } | null = null;
@@ -402,7 +349,6 @@ export default function tpsExtension(pi: ExtensionAPI): void {
402
349
  billedApplied: boolean;
403
350
  ctx: ExtensionContext;
404
351
  } | null = null;
405
- let cachedEntries: Array<{ type?: string; customType?: string }> = [];
406
352
  const tpsCaps = new Map<string, number>();
407
353
  const restoreTimers = new Set<ReturnType<typeof setTimeout>>();
408
354
  let unsubscribeNeuralwatt: (() => void) | undefined;
@@ -411,7 +357,6 @@ export default function tpsExtension(pi: ExtensionAPI): void {
411
357
  currentTiming = null;
412
358
  pendingNeuralwattBilledCost = null;
413
359
  lastCommittedTurn = null;
414
- cachedEntries = [];
415
360
  for (const timer of restoreTimers) clearTimeout(timer);
416
361
  restoreTimers.clear();
417
362
  };
@@ -447,7 +392,6 @@ export default function tpsExtension(pi: ExtensionAPI): void {
447
392
  committed.telemetry = corrected;
448
393
  pi.appendEntry("tps", corrected);
449
394
  pi.events?.emit("tps:telemetry", corrected);
450
- cachedEntries.push({ type: "custom", customType: "tps" });
451
395
  if (committed.ctx.hasUI) committed.ctx.ui.notify(composeDisplayString(corrected), "info");
452
396
  });
453
397
 
@@ -459,14 +403,12 @@ export default function tpsExtension(pi: ExtensionAPI): void {
459
403
 
460
404
  pi.on("session_start", (_event, ctx) => {
461
405
  clearState();
462
- cachedEntries = ctx.sessionManager.getEntries();
463
406
  restoreTPSNotification(ctx, scheduleRestore);
464
407
  });
465
408
 
466
409
  pi.on("session_tree", (_event: SessionTreeEvent, ctx) => {
467
410
  pendingNeuralwattBilledCost = null;
468
411
  lastCommittedTurn = null;
469
- cachedEntries = ctx.sessionManager.getEntries();
470
412
  restoreTPSNotification(ctx, scheduleRestore);
471
413
  });
472
414
 
@@ -572,53 +514,6 @@ export default function tpsExtension(pi: ExtensionAPI): void {
572
514
  pi.appendEntry("tps", telemetry);
573
515
  pi.events?.emit("tps:telemetry", telemetry);
574
516
  if (ctx.hasUI) ctx.ui.notify(composeDisplayString(telemetry), "info");
575
- cachedEntries.push({ type: "custom", customType: "tps" });
576
- });
577
-
578
- pi.registerCommand("tps-export", {
579
- description: i18n.t("tpsExportDescription"),
580
- getArgumentCompletions: (argumentPrefix: string) => {
581
- if ("--full".startsWith(argumentPrefix)) return [{ value: "--full", label: i18n.t("tpsFullFlag") }];
582
- const types = new Set<string>();
583
- for (const entry of cachedEntries) {
584
- if (entry.type === "custom" && entry.customType) types.add(entry.customType);
585
- }
586
- return [...types]
587
- .filter((customType) => customType.startsWith(argumentPrefix))
588
- .map((customType) => ({ value: customType, label: customType }));
589
- },
590
- handler: async (args: string, ctx: ExtensionCommandContext) => {
591
- const tokens = args.trim().split(/\s+/).filter(Boolean);
592
- const full = tokens.includes("--full");
593
- const filterType = tokens.filter((token) => token !== "--full").join(" ") || null;
594
- const entries = (full ? ctx.sessionManager.getEntries() : ctx.sessionManager.getBranch()) as SessionEntryLike[];
595
- const isStructural = (entry: SessionEntryLike) => entry.type === "model_change" || entry.type === "branch_summary";
596
- const matching = entries.filter((entry) => isStructural(entry) ||
597
- (entry.type === "custom" && (!filterType || entry.customType === filterType)));
598
- if (matching.length === 0) {
599
- ctx.ui.notify(i18n.t("tpsNoEntries", { scope: full ? i18n.t("tpsAllEntries") : i18n.t("tpsCurrentBranch") }), "warning");
600
- return;
601
- }
602
- const sessionId = ctx.sessionManager.getSessionId?.() ?? "unknown";
603
- const result = exportEntries(entries, full, filterType, "pi-telemetry", "pi-telemetry", sessionId);
604
- ctx.ui.notify(i18n.t("tpsExported", { count: result.count, filepath: result.filepath }), "info");
605
- },
606
517
  });
607
518
 
608
- pi.registerCommand("session-export", {
609
- description: i18n.t("sessionExportDescription"),
610
- getArgumentCompletions: (argumentPrefix: string) =>
611
- "--full".startsWith(argumentPrefix) ? [{ value: "--full", label: i18n.t("tpsFullFlag") }] : [],
612
- handler: async (args: string, ctx: ExtensionCommandContext) => {
613
- const full = args.trim().split(/\s+/).includes("--full");
614
- const entries = (full ? ctx.sessionManager.getEntries() : ctx.sessionManager.getBranch()) as SessionEntryLike[];
615
- if (entries.length === 0) {
616
- ctx.ui.notify(i18n.t("tpsNoEntries", { scope: full ? i18n.t("tpsAllEntries") : i18n.t("tpsCurrentBranch") }), "warning");
617
- return;
618
- }
619
- const sessionId = ctx.sessionManager.getSessionId?.() ?? "unknown";
620
- const result = exportEntries(entries, full, null, "pi-sessions", "pi-session", sessionId);
621
- ctx.ui.notify(i18n.t("tpsExported", { count: result.count, filepath: result.filepath }), "info");
622
- },
623
- });
624
519
  }