pi-conversation-timer 1.0.0 → 1.0.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.
Files changed (2) hide show
  1. package/index.ts +120 -18
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
1
4
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
5
 
3
6
  /**
@@ -28,6 +31,30 @@ export default function (pi: ExtensionAPI) {
28
31
  let taskCount = 0; // 任务计数
29
32
  let activeContext: ExtensionContext | null = null;
30
33
 
34
+ const cachePath = join(homedir(), ".pi", "agent", "conversation-timer-cache.json");
35
+
36
+ /**
37
+ * 保存当前会话的状态到本地缓存
38
+ */
39
+ function saveStateToCache(ctx: ExtensionContext) {
40
+ try {
41
+ const sessionKey = ctx.sessionManager?.getSessionFile?.() ?? "default";
42
+ let allData: Record<string, any> = {};
43
+ if (existsSync(cachePath)) {
44
+ allData = JSON.parse(readFileSync(cachePath, "utf-8"));
45
+ }
46
+ allData[sessionKey] = {
47
+ totalActiveTimeMs,
48
+ lastTaskDuration,
49
+ taskCount,
50
+ updatedAt: Date.now(),
51
+ };
52
+ writeFileSync(cachePath, JSON.stringify(allData, null, 2), "utf-8");
53
+ } catch {
54
+ // ignore
55
+ }
56
+ }
57
+
31
58
  /**
32
59
  * 生成状态栏文本
33
60
  */
@@ -119,6 +146,18 @@ export default function (pi: ExtensionAPI) {
119
146
  totalActiveTimeMs += duration;
120
147
  lastTaskDuration = duration;
121
148
  currentTaskStartTime = null;
149
+
150
+ // 持久化当前状态到 session 记录和本地缓存,确保 reload 绝不丢失
151
+ try {
152
+ pi.appendEntry("conversation-timer-state", {
153
+ totalActiveTimeMs,
154
+ lastTaskDuration,
155
+ taskCount,
156
+ });
157
+ } catch {
158
+ // ignore
159
+ }
160
+ saveStateToCache(ctx);
122
161
  }
123
162
 
124
163
  // 刷新为静止状态,等待下一次任务发出
@@ -126,34 +165,94 @@ export default function (pi: ExtensionAPI) {
126
165
  }
127
166
 
128
167
  // 会话启动或恢复
129
- pi.on("session_start", async (_event, ctx) => {
168
+ pi.on("session_start", async (event, ctx) => {
130
169
  activeContext = ctx;
131
170
  stopActiveTimer();
132
- totalActiveTimeMs = 0;
133
- taskCount = 0;
134
- lastTaskDuration = null;
135
- currentTaskStartTime = null;
136
171
 
137
- // 尝试从历史消息中恢复交互耗时
172
+ // 只有在明确新开会话(/new)时才归零;reload、resume 等情况必须保留历史时间
173
+ if (event.reason === "new") {
174
+ totalActiveTimeMs = 0;
175
+ taskCount = 0;
176
+ lastTaskDuration = null;
177
+ currentTaskStartTime = null;
178
+ updateStatus(ctx);
179
+ return;
180
+ }
181
+
182
+ let restored = false;
183
+ const sessionKey = ctx.sessionManager?.getSessionFile?.() ?? "default";
184
+
185
+ // 1. 优先从 session 自定义持久化记录中恢复
138
186
  try {
139
187
  const entries = ctx.sessionManager?.getEntries?.() ?? [];
140
- let lastUserTs: number | null = null;
141
188
  for (const entry of entries) {
142
- if ((entry as any).type === "message") {
143
- const msg = (entry as any).message;
144
- if (msg?.role === "user" && typeof msg.timestamp === "number") {
145
- lastUserTs = msg.timestamp;
146
- } else if (msg?.role === "assistant" && typeof msg.timestamp === "number" && lastUserTs !== null) {
147
- const duration = Math.max(0, msg.timestamp - lastUserTs);
148
- totalActiveTimeMs += duration;
149
- lastTaskDuration = duration;
150
- taskCount++;
151
- lastUserTs = null;
189
+ if ((entry as any).type === "custom" && (entry as any).customType === "conversation-timer-state") {
190
+ const data = (entry as any).data;
191
+ if (data && typeof data.totalActiveTimeMs === "number") {
192
+ totalActiveTimeMs = data.totalActiveTimeMs;
193
+ lastTaskDuration = data.lastTaskDuration ?? null;
194
+ taskCount = data.taskCount ?? 0;
195
+ restored = true;
152
196
  }
153
197
  }
154
198
  }
155
199
  } catch {
156
- // 默认从 0 开始
200
+ // ignore
201
+ }
202
+
203
+ // 2. 如果 session 记录中没有,尝试从本地缓存文件中读取
204
+ if (!restored) {
205
+ try {
206
+ if (existsSync(cachePath)) {
207
+ const allData = JSON.parse(readFileSync(cachePath, "utf-8"));
208
+ const cached = allData[sessionKey];
209
+ if (cached && typeof cached.totalActiveTimeMs === "number") {
210
+ totalActiveTimeMs = cached.totalActiveTimeMs;
211
+ lastTaskDuration = cached.lastTaskDuration ?? null;
212
+ taskCount = cached.taskCount ?? 0;
213
+ restored = true;
214
+ }
215
+ }
216
+ } catch {
217
+ // ignore
218
+ }
219
+ }
220
+
221
+ // 3. 兜底:如果未曾记录过(旧会话),从历史消息精准计算
222
+ if (!restored) {
223
+ try {
224
+ const entries = ctx.sessionManager?.getEntries?.() ?? [];
225
+ let curStart: number | null = null;
226
+ let accTime = 0;
227
+ let count = 0;
228
+ let lastDur: number | null = null;
229
+
230
+ for (const entry of entries) {
231
+ if ((entry as any).type === "message") {
232
+ const msg = (entry as any).message;
233
+ if (msg?.role === "user" && typeof msg.timestamp === "number") {
234
+ curStart = msg.timestamp;
235
+ } else if (msg?.role === "assistant" && typeof msg.timestamp === "number" && curStart !== null) {
236
+ // 只有当不是 toolUse 中断时,才算整个任务真正完成
237
+ if (msg.stopReason !== "toolUse") {
238
+ const dur = Math.max(0, msg.timestamp - curStart);
239
+ accTime += dur;
240
+ lastDur = dur;
241
+ count++;
242
+ curStart = null;
243
+ }
244
+ }
245
+ }
246
+ }
247
+
248
+ if (accTime > 0) {
249
+ totalActiveTimeMs = accTime;
250
+ lastTaskDuration = lastDur;
251
+ taskCount = count;
252
+ }
253
+ } catch {
254
+ // ignore
255
+ }
157
256
  }
158
257
 
159
258
  updateStatus(ctx);
@@ -177,6 +276,9 @@ export default function (pi: ExtensionAPI) {
177
276
  // 会话关闭或重置
178
277
  pi.on("session_shutdown", async (_event, ctx) => {
179
278
  stopActiveTimer();
279
+ if (activeContext) {
280
+ saveStateToCache(activeContext);
281
+ }
180
282
  if (ctx && ctx.hasUI) {
181
283
  try {
182
284
  ctx.ui.setStatus("conversation-timer", undefined);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-conversation-timer",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Active task timer extension for Pi coding agent",
5
5
  "author": "Agonieler",
6
6
  "license": "MIT",