@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 +18 -0
- package/README.md +3 -1
- package/cache.ts +7 -0
- package/index.ts +17 -16
- package/package.json +8 -9
- package/providers/zai.ts +4 -4
- package/token-usage.ts +5 -2
- package/types.ts +1 -1
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-
|
|
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
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
4
|
-
"description": "Rich custom status bar for pi with token usage, cost, DeepSeek balance, context window,
|
|
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.
|
|
52
|
-
"@earendil-works/pi-ai": ">=0.
|
|
53
|
-
"@earendil-works/pi-tui": ">=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.
|
|
57
|
-
"@earendil-works/pi-ai": "0.
|
|
58
|
-
"@earendil-works/pi-tui": "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):
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
48
|
+
if (p.toLowerCase().includes("deepseek")) currency = billedInCny ? "\u00a5" : "$";
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
return { input, output, cacheRead, cacheWrite, total, hitRate, cost, currency };
|