@fyeeme/pi-statusline 1.1.0 → 1.1.1

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/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.1] - 2026-08-08
11
+
12
+ ### Fixed
13
+
14
+ - 会话级 token/cost/tok-s 在分叉会话(`/fork`、`/tree`)后不再把整棵树计入:`SessionTokenUsageCalculator.compute` 与 `agent_end` 累计输出改用 `sessionManager.getBranch()`(仅当前分支);`/status-debug` 仍保留全量 `getEntries()` 用于诊断。
15
+ - 切换 provider(`/model` deepseek↔zai)后状态栏不再短暂显示上一个 provider 的用量段:`getCachedUsage` 在缓存 provider 与当前 provider 不匹配时返回 null,等待 `model_select` 触发的新刷新落地。
16
+ - ZAI 用量段对 `zai-coding-cn` provider 不再永久漏显示:`ZaiResult.provider` 记录实际 provider key(`zai`/`zai-coding-cn`),provider guard 不再因字面量 `"zai"` 误判(code-review #1)。
17
+ - tok/s 改用本次 agent run 的 output/耗时(`event.messages`),分子分母同口径,不再受 /fork 放弃分支的累计耗时影响;同时消除 `agent_end` 对模块级 `lastCtx` 的依赖(code-review #2)。
18
+ - `/status-debug` 的 msg 遍历改用 `getBranch()`,与 `tokenCalculator.compute` 口径一致(code-review #7)。
19
+ - `SessionTokenUsageCalculator.compute` 内 `deepSeekBilledInCny()` 冗余的第二次调用复用已有变量(code-review #8)。
20
+
21
+ ### Changed
22
+
23
+ - 移除 `package.json` 中已废弃的 MCP 描述与 keyword(MCP 状态段自 1.0.2 起已移除)。
24
+ - 新增 `session_shutdown` 清理 usage cache:`/resume` 或 `/new` 进入不同 provider 时立即刷新,而非沿用上一会话缓存。
25
+ - `devDependencies` 对齐当前平台(`@earendil-works/pi-coding-agent`/`pi-ai`/`pi-tui` `0.77.0` → `0.84.1`),便于利用 `thinking_level_select`、`getAvailableProviderCount` 等新类型。
26
+ - README 状态栏示例 `deepseek-v3` → `deepseek-v4-pro`(`deepseek-v3` 已不在 0.84.x 模型库)。
27
+
10
28
  ## [1.1.0] - 2026-08-07
11
29
 
12
30
  ### Added
package/README.md CHANGED
@@ -95,7 +95,7 @@ See the Pi Packages guide on [pi.dev](https://pi.dev) for the full list of sourc
95
95
  ## Status Bar Layout
96
96
 
97
97
  ```
98
- ~/projects/my-repo (main) deepseek-v3 · xhigh
98
+ ~/projects/my-repo (main) deepseek-v4-pro · xhigh
99
99
  tokens 65k(in 12k, out 8k, cache 45k) · ¥0.12/50.00 · 7d:1.2M · 45.2%/64k · 2m30s 38.2tok/s
100
100
  ```
101
101
 
@@ -120,6 +120,8 @@ The extension registers a `UsageProvider` per supported provider and refreshes u
120
120
 
121
121
  All network calls are best-effort with a 5-second timeout — failures stay silent and never block the status bar.
122
122
 
123
+ > **Platform API note:** the custom footer relies on `ctx.ui.setFooter`'s third parameter — `footerData: ReadonlyFooterDataProvider` (for `getGitBranch`/`getExtensionStatuses`/`onBranchChange`) — and on returning `dispose` from the footer factory. These are part of the 0.84.x `ExtensionAPI` surface (see `dist/core/extensions/types.d.ts`), but pi's `docs/extensions.md` currently documents only the `(tui, theme)` form. If a future pi release changes the `setFooter` signature, check this extension's footer wiring first.
124
+
123
125
  ## Changelog
124
126
 
125
127
  See [CHANGELOG.md](CHANGELOG.md).
package/cache.ts CHANGED
@@ -70,6 +70,13 @@ export function getCachedUsage(
70
70
  // may synchronously set usageCache = null when the provider is not found,
71
71
  // which would make the eventual `return cache.result` throw on null.
72
72
  const cache = usageCache;
73
+ // Drop stale cache if the active provider changed since it was populated
74
+ // (e.g. deepseek -> zai via /model). model_select already kicked off a
75
+ // refresh; returning null avoids briefly showing the previous provider's
76
+ // segment until the new fetch lands.
77
+ if (lastModel && cache.result && cache.result.provider !== lastModel.provider) {
78
+ return null;
79
+ }
73
80
  if (Date.now() - cache.fetchedAt > CACHE_TTL_MS) {
74
81
  // TTL expired: return stale data but trigger background refresh.
75
82
  // Memoize inflight promise so concurrent callers share one fetch.
package/index.ts CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  setCostCurrencyOverride,
28
28
  getCostCurrencyOverride,
29
29
  } from "./cost.ts";
30
- import { refreshUsage, getCachedUsage, getUsageCacheAge } from "./cache.ts";
30
+ import { refreshUsage, getCachedUsage, getUsageCacheAge, resetUsageCache } from "./cache.ts";
31
31
  import { formatCwd, buildStatLine, buildInfoLine } from "./footer.ts";
32
32
 
33
33
  // ---------------------------------------------------------------------------
@@ -79,21 +79,14 @@ export default function (pi: ExtensionAPI) {
79
79
  if (elapsedMs <= 0) return;
80
80
  lastElapsedSec += elapsedMs / 1000;
81
81
 
82
- // Compute cumulative output tokens from all assistant messages in the session
83
- let cumulativeOutput = 0;
84
- if (lastCtx) {
85
- for (const entry of lastCtx.sessionManager.getEntries()) {
86
- if (entry.type === "message" && entry.message.role === "assistant") {
87
- cumulativeOutput += (entry.message as AssistantMessage).usage.output;
88
- }
89
- }
90
- } else {
91
- // fallback: use current turn's messages when no session context
92
- for (const m of event.messages) {
93
- if (m.role === "assistant") cumulativeOutput += (m as AssistantMessage).usage.output;
94
- }
82
+ // tok/s = 本次 agent run 的 output / 本次 run 耗时(即时速度)。用 event.messages
83
+ // 而非 sessionManager 遍历:不依赖 lastCtx/分支语义,不受 /fork 放弃分支的耗时
84
+ // 累计影响(分子分母同为本 run 口径),也避开 lastCtx 在 session 切换边界的 staleness。
85
+ let turnOutput = 0;
86
+ for (const m of event.messages) {
87
+ if (m.role === "assistant") turnOutput += (m as AssistantMessage).usage.output;
95
88
  }
96
- lastTps = cumulativeOutput > 0 ? cumulativeOutput / lastElapsedSec : 0;
89
+ lastTps = turnOutput > 0 ? turnOutput / (elapsedMs / 1000) : 0;
97
90
 
98
91
  // Schedule background refresh to pick up new usage data
99
92
  if (lastCtx && lastModel) {
@@ -132,8 +125,10 @@ export default function (pi: ExtensionAPI) {
132
125
  const cacheAge = getUsageCacheAge();
133
126
  w(` cacheAge: ${cacheAge !== null ? `${Math.round(cacheAge / 1000)}s` : "N/A"}`);
134
127
 
128
+ // status-debug dumps the current branch (same scope tokenCalculator.compute
129
+ // uses via getBranch), so the msg count (idx) lines up with computed totals.
135
130
  let idx = 0;
136
- for (const entry of ctx.sessionManager.getEntries()) {
131
+ for (const entry of ctx.sessionManager.getBranch()) {
137
132
  if (entry.type === "message" && entry.message.role === "assistant") {
138
133
  const m = entry.message as AssistantMessage;
139
134
  w(`--- msg[${idx}] ---`);
@@ -188,6 +183,12 @@ export default function (pi: ExtensionAPI) {
188
183
  }
189
184
  });
190
185
 
186
+ // ---- Session shutdown: drop usage cache so /resume into a different provider
187
+ // shows fresh data instead of the previous session's cached segment.
188
+ pi.on("session_shutdown", () => {
189
+ resetUsageCache();
190
+ });
191
+
191
192
  // ---- Footer ----
192
193
  pi.on("session_start", (_event, ctx) => {
193
194
  if (!ctx.hasUI) return;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fyeeme/pi-statusline",
3
- "version": "1.1.0",
4
- "description": "Rich custom status bar for pi with token usage, cost, DeepSeek balance, context window, timing, and MCP connection status.",
3
+ "version": "1.1.1",
4
+ "description": "Rich custom status bar for pi with token usage, cost, DeepSeek balance, context window, and timing.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "fyeeme",
@@ -27,7 +27,6 @@
27
27
  "deepseek",
28
28
  "tokens",
29
29
  "cost",
30
- "mcp",
31
30
  "balance",
32
31
  "footer"
33
32
  ],
@@ -48,14 +47,14 @@
48
47
  "typecheck": "tsc"
49
48
  },
50
49
  "peerDependencies": {
51
- "@earendil-works/pi-coding-agent": ">=0.77.0",
52
- "@earendil-works/pi-ai": ">=0.77.0",
53
- "@earendil-works/pi-tui": ">=0.77.0"
50
+ "@earendil-works/pi-coding-agent": ">=0.84.1",
51
+ "@earendil-works/pi-ai": ">=0.84.1",
52
+ "@earendil-works/pi-tui": ">=0.84.1"
54
53
  },
55
54
  "devDependencies": {
56
- "@earendil-works/pi-coding-agent": "0.77.0",
57
- "@earendil-works/pi-ai": "0.77.0",
58
- "@earendil-works/pi-tui": "0.77.0",
55
+ "@earendil-works/pi-coding-agent": "0.84.1",
56
+ "@earendil-works/pi-ai": "0.84.1",
57
+ "@earendil-works/pi-tui": "0.84.1",
59
58
  "@types/node": "22.19.19",
60
59
  "typescript": "5.9.3"
61
60
  }
package/providers/zai.ts CHANGED
@@ -5,7 +5,7 @@ import { fmt, formatCountdown, formatWeeklyCountdown } from "../footer.ts";
5
5
  import { startOfCurrentWeekLocal, startOfNextWeekLocal } from "../week.ts";
6
6
 
7
7
  /** Check if the provider name is a ZAI/GLM variant. */
8
- function isZaiProvider(provider: string): boolean {
8
+ function isZaiProvider(provider: string): provider is "zai" | "zai-coding-cn" {
9
9
  return provider === "zai" || provider === "zai-coding-cn";
10
10
  }
11
11
 
@@ -84,7 +84,7 @@ export class ZaiUsageProvider implements UsageProvider {
84
84
  }
85
85
 
86
86
  return {
87
- provider: "zai",
87
+ provider: model.provider,
88
88
  tokensLimitPct: fiveHourLimit?.percentage ?? 0,
89
89
  tokensResetAt: fiveHourLimit?.nextResetTime ?? 0,
90
90
  level,
@@ -99,7 +99,7 @@ export class ZaiUsageProvider implements UsageProvider {
99
99
  }
100
100
 
101
101
  formatForFooter(result: NonNullable<ProviderUsageResult>, _sessionCost: number, _currency: string): string {
102
- if (result.provider !== "zai") return "";
102
+ if (!isZaiProvider(result.provider)) return "";
103
103
  const zai = result as ZaiResult;
104
104
  const parts: string[] = [];
105
105
 
@@ -121,7 +121,7 @@ export class ZaiUsageProvider implements UsageProvider {
121
121
  }
122
122
 
123
123
  debugDump(result: NonNullable<ProviderUsageResult>, w: (s: string) => void): void {
124
- if (result.provider !== "zai") return;
124
+ if (!isZaiProvider(result.provider)) return;
125
125
  const zai = result as ZaiResult;
126
126
  w(` tokensLimitPct: ${zai.tokensLimitPct}%`);
127
127
  w(` tokensResetAt: ${new Date(zai.tokensResetAt).toISOString()}`);
package/token-usage.ts CHANGED
@@ -20,7 +20,10 @@ export class SessionTokenUsageCalculator implements TokenUsageCalculator {
20
20
 
21
21
  const billedInCny = deepSeekBilledInCny();
22
22
 
23
- for (const entry of ctx.sessionManager.getEntries()) {
23
+ // Use getBranch() (current branch only) so token/cost stats exclude abandoned
24
+ // sibling branches after /fork or /tree navigation. getEntries() would sum
25
+ // the whole tree and overcount in forked sessions.
26
+ for (const entry of ctx.sessionManager.getBranch()) {
24
27
  if (entry.type === "message" && entry.message.role === "assistant") {
25
28
  const m = entry.message as AssistantMessage;
26
29
  input += m.usage.input;
@@ -42,7 +45,7 @@ export class SessionTokenUsageCalculator implements TokenUsageCalculator {
42
45
  currency = override;
43
46
  } else {
44
47
  const p = ctx.model?.provider ?? "";
45
- if (p.toLowerCase().includes("deepseek")) currency = deepSeekBilledInCny() ? "\u00a5" : "$";
48
+ if (p.toLowerCase().includes("deepseek")) currency = billedInCny ? "\u00a5" : "$";
46
49
  }
47
50
 
48
51
  return { input, output, cacheRead, cacheWrite, total, hitRate, cost, currency };
package/types.ts CHANGED
@@ -30,7 +30,7 @@ export type DeepSeekResult = {
30
30
  };
31
31
 
32
32
  export type ZaiResult = {
33
- provider: "zai";
33
+ provider: "zai" | "zai-coding-cn";
34
34
  tokensLimitPct: number;
35
35
  tokensResetAt: number;
36
36
  level: string;