token-stats-timer 1.0.18 → 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/README.md CHANGED
@@ -20,6 +20,31 @@ Footer 上行(左对齐):
20
20
 
21
21
  Footer 下行:cwd + git 分支 + 其他扩展状态。
22
22
 
23
+ ## 按模型自动记忆思考强度(thinking-memory)
24
+
25
+ 不用命令手动设置,**手动切换思考强度时自动记录**为当前模型的默认级别,
26
+ 会话启动 / 切换模型时自动恢复。参考 `@tifan/pi-preferred-thinking` 的按模型偏好机制,
27
+ 改为以实际切换行为作为记忆来源(默认开启):
28
+
29
+ - 手动切换 thinking level(快捷键 / /thinking / 设置界面)→ 自动记录到当前模型
30
+ - `session_start` / `model_select` → 自动应用该模型记忆的级别
31
+ - 自身 `setThinkingLevel` 与模型切换引发的级别变化(如不支持 max 被 clamp)不会误记录
32
+
33
+ - `/auto-remember-thinking-level` —— 无参查看状态;`on` / `off` 启用或禁用(默认开启)
34
+
35
+ 配置:`~/.pi/agent/extensions/token-stats/auto-remember-thinking-level.json`
36
+
37
+ ```json
38
+ {
39
+ "enabled": true,
40
+ "levels": {
41
+ "opencode-go/deepseek-v4-flash": "max"
42
+ }
43
+ }
44
+ ```
45
+
46
+ > 若同时安装原 `@tifan/pi-preferred-thinking`,其 session_start 自动应用会与本功能互相覆盖,建议二选一。
47
+
23
48
  ## macOS 完成通知
24
49
 
25
50
  每次 run(agent 任务)结束后弹系统通知,区分两种结果:
@@ -81,6 +106,7 @@ Footer 下行:cwd + git 分支 + 其他扩展状态。
81
106
  - `/stats limit` —— 套餐配置(为当前 provider 选择/关闭配额套餐;选 GLM 后会继续询问是否配置团队套餐凭证)
82
107
  - `/stats config` —— 显示样式 / 显示内容 / 配额刷新时间 / GLM 团队凭证
83
108
  - `/notify [on|off|test]` —— 通知开关 / 测试(无参查看状态)
109
+ - `/auto-remember-thinking-level [on|off]` —— 自动记忆思考强度开关(无参查看状态)
84
110
 
85
111
  ## GLM 团队套餐(Team Plan)
86
112
 
@@ -88,7 +114,7 @@ Footer 下行:cwd + git 分支 + 其他扩展状态。
88
114
 
89
115
  本插件在**组织 ID 与项目 ID 都配置**时才走团队查询,否则回退个人版查询:
90
116
 
91
- - 启用 GLM 套餐后(`/stats` 选 GLM)会自动弹出团队凭证配置询问,可「✏️ 配置/修改」或「跳过」
117
+ - 启用 GLM 套餐后(`/stats limit` 选 GLM)会自动弹出团队凭证配置询问,可「✏️ 配置/修改」或「跳过」
92
118
  - 随时可通过 `/stats config` → 「GLM 团队凭证」修改或清除
93
119
  - 凭证保存在 `~/.pi/agent/extensions/token-stats/config.json` 的 `teamCredential` 字段:
94
120
 
package/index.ts CHANGED
@@ -10,6 +10,9 @@
10
10
  // 模块划分:
11
11
  // run-timer.ts —— run 计时状态机 + session 持久化(原 pi-run-timer)
12
12
  // token-stats.ts —— token 统计 + 套餐配额 + JSONL 日志 + /stats(原 token-stats)
13
+ // notify.ts —— macOS 完成通知(成功/失败/中止)
14
+ // step-timer.ts —— 任务计时(Working 实时耗时 + 总耗时汇总)
15
+ // thinking-memory.ts —— 按模型自动记忆思考强度(手动切换时记录,默认开启)
13
16
  //
14
17
  // 与两个原包的兼容性:
15
18
  // - 配置沿用 ~/.pi/agent/extensions/token-stats/{config.json,display-config.json}
@@ -21,6 +24,7 @@ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
21
24
  import { createTokenStats, formatUserPath, type SharedState } from "./token-stats.ts";
22
25
  import { createNotifier } from "./notify.ts";
23
26
  import { createStepTimer } from "./step-timer.ts";
27
+ import { createThinkingMemory } from "./thinking-memory.ts";
24
28
 
25
29
  const shared: SharedState = {
26
30
  sessionActive: false,
@@ -33,6 +37,8 @@ export default function runTokenStatsExtension(pi: ExtensionAPI) {
33
37
  createNotifier(pi);
34
38
  // 每步耗时:Thinking.../Working... 实时耗时 + 每 turn/总耗时会话摘要
35
39
  createStepTimer(pi);
40
+ // 按模型自动记忆思考强度(/auto-remember-thinking-level),默认开启
41
+ createThinkingMemory(pi);
36
42
 
37
43
  pi.on("session_start", (_event, ctx) => {
38
44
  shared.sessionActive = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-stats-timer",
3
- "version": "1.0.18",
3
+ "version": "1.1.1",
4
4
  "description": "pi extension to display token usage and time spend.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -16,6 +16,7 @@
16
16
  "token-stats.ts",
17
17
  "notify.ts",
18
18
  "step-timer.ts",
19
+ "thinking-memory.ts",
19
20
  "README.md"
20
21
  ],
21
22
  "scripts": {
@@ -0,0 +1,141 @@
1
+ // thinking-memory 模块 —— 按模型自动记忆思考强度
2
+ // =============================================================================
3
+ // 行为(默认开启,/auto-remember-thinking-level on|off 切换):
4
+ // - 用户手动切换 thinking level 时,自动记录为当前模型的默认级别
5
+ // - session_start / model_select 时自动应用该模型记忆的级别
6
+ // - 扩展自身 setThinkingLevel 与模型切换引起的级别变化不会被误记录
7
+ //
8
+ // 配置:~/.pi/agent/extensions/token-stats/auto-remember-thinking-level.json
9
+ // {
10
+ // "enabled": true,
11
+ // "levels": { "opencode-go/deepseek-v4-flash": "max" }
12
+ // }
13
+ //
14
+ // 参考 @tifan/pi-preferred-thinking(手动设置命令)改为自动记忆模式:不提供
15
+ // 单独设置命令,以用户的实际切换行为作为记忆来源。
16
+
17
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
+ import { homedir } from "node:os";
20
+ import { join } from "node:path";
21
+
22
+ type ThinkingLevel = Parameters<ExtensionAPI["setThinkingLevel"]>[0];
23
+
24
+ const CONFIG_DIR = join(homedir(), ".pi/agent/extensions/token-stats");
25
+ const CONFIG_FILE = join(CONFIG_DIR, "auto-remember-thinking-level.json");
26
+
27
+ const VALID_LEVELS = new Set<ThinkingLevel>([
28
+ "off",
29
+ "minimal",
30
+ "low",
31
+ "medium",
32
+ "high",
33
+ "xhigh",
34
+ "max",
35
+ ]);
36
+
37
+ interface ThinkingMemoryConfig {
38
+ enabled: boolean;
39
+ levels: Record<string, ThinkingLevel>;
40
+ }
41
+
42
+ const DEFAULT_CONFIG: ThinkingMemoryConfig = { enabled: true, levels: {} };
43
+
44
+ /** 判定「手动切换」的窗口:距模型切换多少毫秒内的级别变化视为切换引起 */
45
+ const MODEL_SWITCH_WINDOW_MS = 200;
46
+ /** 自身 apply 引起的级别变化要在多少毫秒内匹配上才算(防止残留标志误吞手动切换) */
47
+ const APPLY_WINDOW_MS = 100;
48
+
49
+ function loadConfig(): ThinkingMemoryConfig {
50
+ try {
51
+ if (!existsSync(CONFIG_FILE)) return { ...DEFAULT_CONFIG, levels: {} };
52
+ const raw = JSON.parse(readFileSync(CONFIG_FILE, "utf-8")) as unknown;
53
+ const cfg = raw && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
54
+ const levels: Record<string, ThinkingLevel> = {};
55
+ const configured = cfg.levels;
56
+ if (configured && typeof configured === "object" && !Array.isArray(configured)) {
57
+ for (const [modelKey, level] of Object.entries(configured)) {
58
+ if (typeof level === "string" && VALID_LEVELS.has(level as ThinkingLevel)) {
59
+ levels[modelKey] = level as ThinkingLevel;
60
+ }
61
+ }
62
+ }
63
+ return { enabled: cfg.enabled !== false, levels };
64
+ } catch {
65
+ return { ...DEFAULT_CONFIG, levels: {} };
66
+ }
67
+ }
68
+
69
+ function saveConfig(cfg: ThinkingMemoryConfig): void {
70
+ mkdirSync(CONFIG_DIR, { recursive: true });
71
+ writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
72
+ }
73
+
74
+ export function createThinkingMemory(pi: ExtensionAPI): void {
75
+ let cfg = loadConfig();
76
+
77
+ // 自身 apply 的记录:apply 时间 + 目标级别,事件匹配且窗口内则视为自身引起
78
+ let lastApply: { at: number; level: ThinkingLevel } | null = null;
79
+ // 最近一次模型切换时间戳(判断级别变化是否由切模型/clamp 引起)
80
+ let lastModelSwitchAt = 0;
81
+
82
+ /** 应用当前模型的记忆级别(自身 setThinkingLevel 不会触发误记录) */
83
+ function applyForModel(ctx: ExtensionContext): void {
84
+ if (!cfg.enabled) return;
85
+ if (!ctx.model) return;
86
+ const modelKey = `${ctx.model.provider}/${ctx.model.id}`;
87
+ const level = cfg.levels[modelKey];
88
+ if (!level) return;
89
+ if (pi.getThinkingLevel() === level) return;
90
+
91
+ lastApply = { at: Date.now(), level };
92
+ pi.setThinkingLevel(level);
93
+ }
94
+
95
+ pi.registerCommand("auto-remember-thinking-level", {
96
+ description: "自动记忆思考强度: on | off(无参查看状态)",
97
+ handler: (args, ctx) => {
98
+ const arg = args.trim();
99
+ if (arg === "on" || arg === "off") {
100
+ cfg = { ...cfg, enabled: arg === "on" };
101
+ saveConfig(cfg);
102
+ ctx.ui.notify(`自动记忆思考强度已${arg === "on" ? "开启" : "关闭"}`, "info");
103
+ } else {
104
+ ctx.ui.notify(`自动记忆思考强度: ${cfg.enabled ? "开" : "关"}`, "info");
105
+ }
106
+ },
107
+ });
108
+
109
+ pi.on("session_start", (_event, ctx) => {
110
+ applyForModel(ctx);
111
+ });
112
+
113
+ pi.on("model_select", (_event, ctx) => {
114
+ lastModelSwitchAt = Date.now();
115
+ applyForModel(ctx);
116
+ });
117
+
118
+ pi.on("thinking_level_select", (event, ctx) => {
119
+ if (!cfg.enabled) return;
120
+ if (!ctx.model) return;
121
+ const level = event.level as ThinkingLevel;
122
+ if (!VALID_LEVELS.has(level)) return;
123
+
124
+ // 自身 apply(session_start / model_select 恢复记忆)引起的变化:忽略
125
+ if (lastApply && Date.now() - lastApply.at < APPLY_WINDOW_MS && level === lastApply.level) {
126
+ lastApply = null;
127
+ return;
128
+ }
129
+
130
+ const modelKey = `${ctx.model.provider}/${ctx.model.id}`;
131
+ // 模型切换时 level 变化先于 model_select 发出,故延迟确认:
132
+ // 窗口内 lastModelSwitchAt 发生变化(发生了模型切换)则丢弃,防止误记录
133
+ const switchedAtSnapshot = lastModelSwitchAt;
134
+ setTimeout(() => {
135
+ if (lastModelSwitchAt !== switchedAtSnapshot) return;
136
+ if (cfg.levels[modelKey] === level) return;
137
+ cfg = { ...cfg, levels: { ...cfg.levels, [modelKey]: level } };
138
+ saveConfig(cfg);
139
+ }, MODEL_SWITCH_WINDOW_MS);
140
+ });
141
+ }