token-stats-timer 1.0.7 → 1.0.9
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 +5 -4
- package/notify.ts +4 -30
- package/package.json +1 -1
- package/step-timer.ts +5 -80
package/README.md
CHANGED
|
@@ -57,12 +57,13 @@ Footer 下行:cwd + git 分支 + 其他扩展状态。
|
|
|
57
57
|
## 任务计时(step-timer)
|
|
58
58
|
|
|
59
59
|
- 任务执行中:工作指示器(spinner 文案)显示 `Working... 01:02`(整体已耗时,每秒刷新)
|
|
60
|
-
-
|
|
61
|
-
- 完成后:会话末尾插入一条汇总 `✅ 任务完成 总耗时 01:23`(失败时 ❌,中止时 ⏹),经 `appendEntry` 持久化、不进入 LLM 上下文,`/resume` 后仍在
|
|
60
|
+
- 完成后:会话末尾插入一条汇总,仅显示总耗时:
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
`总耗时 01:23`
|
|
64
63
|
|
|
65
|
-
|
|
64
|
+
经 `appendEntry` 持久化、不进入 LLM 上下文,`/resume` 后仍在;无主题色样式。
|
|
65
|
+
|
|
66
|
+
无独立开关,随包启用;计时口径与 run-timer 一致(一次 run = 首个 agent_start → agent_settled,含重试/压缩/排队提示)。早期版本的每 turn 摘要(timing-turn)、“⏱ 思考”每条消息内联标注与 Thinking 实时标签均已移除,历史条目自动隐藏。
|
|
66
67
|
|
|
67
68
|
## 命令
|
|
68
69
|
|
package/notify.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// enabled 总开关(默认 true)
|
|
10
10
|
// minDurationSec 时长低于该秒数的 run 不通知(默认 0 = 每次都通知)
|
|
11
11
|
// sound 通知声音(默认 "Glass","" 表示静音)
|
|
12
|
-
// onSuccess /
|
|
12
|
+
// onSuccess / onAbort / onSessionEnd 各类通知开关
|
|
13
13
|
//
|
|
14
14
|
// 命令:/notify on | off | status
|
|
15
15
|
|
|
@@ -27,7 +27,6 @@ export interface NotifyConfig {
|
|
|
27
27
|
minDurationSec: number;
|
|
28
28
|
sound: string;
|
|
29
29
|
onSuccess: boolean;
|
|
30
|
-
onFailure: boolean;
|
|
31
30
|
onAbort: boolean;
|
|
32
31
|
onSessionEnd: boolean;
|
|
33
32
|
}
|
|
@@ -37,7 +36,6 @@ const DEFAULT_CONFIG: NotifyConfig = {
|
|
|
37
36
|
minDurationSec: 0,
|
|
38
37
|
sound: "Glass",
|
|
39
38
|
onSuccess: true,
|
|
40
|
-
onFailure: true,
|
|
41
39
|
onAbort: false,
|
|
42
40
|
onSessionEnd: false,
|
|
43
41
|
};
|
|
@@ -80,9 +78,7 @@ export function createNotifier(pi: ExtensionAPI): void {
|
|
|
80
78
|
|
|
81
79
|
// 当前 run 的状态(agent_start 重置,agent_end 收集,agent_settled 消费)
|
|
82
80
|
let runStartMs: number | undefined;
|
|
83
|
-
let runFailed = false;
|
|
84
81
|
let runAborted = false;
|
|
85
|
-
let runErrorMsg: string | undefined;
|
|
86
82
|
|
|
87
83
|
// 进程内去抖计时
|
|
88
84
|
let lastNotifyAt = 0;
|
|
@@ -102,9 +98,7 @@ export function createNotifier(pi: ExtensionAPI): void {
|
|
|
102
98
|
}
|
|
103
99
|
|
|
104
100
|
pi.on("agent_start", () => {
|
|
105
|
-
runFailed = false;
|
|
106
101
|
runAborted = false;
|
|
107
|
-
runErrorMsg = undefined;
|
|
108
102
|
if (runStartMs === undefined) runStartMs = Date.now();
|
|
109
103
|
});
|
|
110
104
|
|
|
@@ -115,17 +109,7 @@ export function createNotifier(pi: ExtensionAPI): void {
|
|
|
115
109
|
if (m.role === "assistant") {
|
|
116
110
|
if (m.stopReason === "aborted") {
|
|
117
111
|
runAborted = true;
|
|
118
|
-
runErrorMsg = runErrorMsg || "被用户中止";
|
|
119
|
-
} else if (m.stopReason === "error" || m.errorMessage) {
|
|
120
|
-
runFailed = true;
|
|
121
|
-
runErrorMsg = runErrorMsg || m.errorMessage || "模型调用出错";
|
|
122
112
|
}
|
|
123
|
-
} else if (m.role === "toolResult" && m.isError) {
|
|
124
|
-
runFailed = true;
|
|
125
|
-
const toolMsg = `工具 ${m.toolName} 执行失败`;
|
|
126
|
-
runErrorMsg = runErrorMsg && !runErrorMsg.startsWith("工具 ")
|
|
127
|
-
? runErrorMsg
|
|
128
|
-
: toolMsg;
|
|
129
113
|
}
|
|
130
114
|
}
|
|
131
115
|
});
|
|
@@ -142,19 +126,11 @@ export function createNotifier(pi: ExtensionAPI): void {
|
|
|
142
126
|
|
|
143
127
|
const dur = startMs !== undefined ? `${formatDuration(durationMs)}` : "-";
|
|
144
128
|
|
|
145
|
-
if (
|
|
146
|
-
if (config.onFailure) {
|
|
147
|
-
notify(
|
|
148
|
-
"❌ pi 执行失败",
|
|
149
|
-
`耗时:${dur}\n${truncate(runErrorMsg || "未知错误")}`,
|
|
150
|
-
config.sound,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
} else if (runAborted) {
|
|
129
|
+
if (runAborted) {
|
|
154
130
|
if (config.onAbort) {
|
|
155
131
|
notify(
|
|
156
132
|
"⏹ pi 已中止",
|
|
157
|
-
`耗时:${dur}\n${truncate(
|
|
133
|
+
`耗时:${dur}\n${truncate("执行被中止")}`,
|
|
158
134
|
config.sound,
|
|
159
135
|
);
|
|
160
136
|
}
|
|
@@ -167,9 +143,7 @@ export function createNotifier(pi: ExtensionAPI): void {
|
|
|
167
143
|
}
|
|
168
144
|
|
|
169
145
|
// 消费本次结果,避免串扰下一次 run
|
|
170
|
-
runFailed = false;
|
|
171
146
|
runAborted = false;
|
|
172
|
-
runErrorMsg = undefined;
|
|
173
147
|
});
|
|
174
148
|
|
|
175
149
|
// 会话关闭时提示(可选)
|
|
@@ -196,7 +170,7 @@ export function createNotifier(pi: ExtensionAPI): void {
|
|
|
196
170
|
const sound = config.sound ? `, 声音=${config.sound}` : ", 静音";
|
|
197
171
|
ctx.ui.notify(
|
|
198
172
|
`完成通知: ${config.enabled ? "开" : "关"}, 最短时长=${config.minDurationSec}s, ` +
|
|
199
|
-
`成功=${config.onSuccess ? "开" : "关"},
|
|
173
|
+
`成功=${config.onSuccess ? "开" : "关"}, ` +
|
|
200
174
|
`中止=${config.onAbort ? "开" : "关"}, 会话结束=${config.onSessionEnd ? "开" : "关"}${sound}`,
|
|
201
175
|
"info",
|
|
202
176
|
);
|
package/package.json
CHANGED
package/step-timer.ts
CHANGED
|
@@ -2,15 +2,11 @@
|
|
|
2
2
|
// =============================================================================
|
|
3
3
|
// 实时显示(仅交互 TUI):
|
|
4
4
|
// - 任务执行中:工作指示器文案显示 "Working... 01:02"(整体已耗时,每秒刷新)
|
|
5
|
-
// - 思考阶段:隐藏思考块文案显示 "Thinking... 00:45"(当前思考块已耗时)
|
|
6
5
|
// 完成后汇总(appendEntry 持久化,不进入 LLM 上下文,resume 后可回看):
|
|
7
|
-
// - agent_settled appendEntry("timing-final", …)
|
|
6
|
+
// - agent_settled appendEntry("timing-final", …):仅本次任务总耗时
|
|
8
7
|
//
|
|
9
8
|
// 一次 run = 空闲后的首个 agent_start → agent_settled(与 run-timer 语义一致,
|
|
10
9
|
// 包含重试、压缩恢复和排队的 steering/follow-up 提示)。
|
|
11
|
-
//
|
|
12
|
-
// 注意:早期版本曾用 "timing-turn" 每 turn 摘要,该渲染器已移除,
|
|
13
|
-
// 历史遗留的 timing-turn 条目因无渲染器而自动不再显示。
|
|
14
10
|
|
|
15
11
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
16
12
|
import { Box, Text } from "@earendil-works/pi-tui";
|
|
@@ -18,8 +14,6 @@ import { Box, Text } from "@earendil-works/pi-tui";
|
|
|
18
14
|
/** 一次 run 的总耗时汇总(appendEntry "timing-final") */
|
|
19
15
|
export interface FinalTimingData {
|
|
20
16
|
totalMs: number;
|
|
21
|
-
failed: boolean;
|
|
22
|
-
aborted: boolean;
|
|
23
17
|
}
|
|
24
18
|
|
|
25
19
|
const FINAL_TYPE = "timing-final";
|
|
@@ -43,29 +37,12 @@ export function createStepTimer(pi: ExtensionAPI): void {
|
|
|
43
37
|
|
|
44
38
|
let runActive = false;
|
|
45
39
|
let runStartMs = 0;
|
|
46
|
-
let runFailed = false;
|
|
47
|
-
let runAborted = false;
|
|
48
|
-
|
|
49
|
-
// 思考阶段状态(仅用于 Thinking... 文案计时)
|
|
50
|
-
let thinkingSinceMs: number | undefined;
|
|
51
|
-
|
|
52
|
-
/** 结束思考阶段并恢复默认 "Thinking..." 文案(若当前处于思考中) */
|
|
53
|
-
function endThinking(): void {
|
|
54
|
-
if (thinkingSinceMs === undefined) return;
|
|
55
|
-
thinkingSinceMs = undefined;
|
|
56
|
-
if (lastCtx?.hasUI) lastCtx.ui.setHiddenThinkingLabel();
|
|
57
|
-
}
|
|
58
40
|
|
|
59
41
|
function startTicker(): void {
|
|
60
42
|
stopTicker();
|
|
61
43
|
tick = setInterval(() => {
|
|
62
44
|
if (!lastCtx?.hasUI || !runActive) return;
|
|
63
|
-
|
|
64
|
-
const ui = lastCtx.ui;
|
|
65
|
-
ui.setWorkingMessage(`Working... ${formatDuration(now - runStartMs)}`);
|
|
66
|
-
if (thinkingSinceMs !== undefined) {
|
|
67
|
-
ui.setHiddenThinkingLabel(`Thinking... ${formatDuration(now - thinkingSinceMs)}`);
|
|
68
|
-
}
|
|
45
|
+
lastCtx.ui.setWorkingMessage(`Working... ${formatDuration(Date.now() - runStartMs)}`);
|
|
69
46
|
}, TICK_MS);
|
|
70
47
|
}
|
|
71
48
|
|
|
@@ -78,8 +55,6 @@ export function createStepTimer(pi: ExtensionAPI): void {
|
|
|
78
55
|
function appendFinal(): void {
|
|
79
56
|
pi.appendEntry<FinalTimingData>(FINAL_TYPE, {
|
|
80
57
|
totalMs: Date.now() - runStartMs,
|
|
81
|
-
failed: runFailed,
|
|
82
|
-
aborted: runAborted,
|
|
83
58
|
});
|
|
84
59
|
}
|
|
85
60
|
|
|
@@ -94,57 +69,13 @@ export function createStepTimer(pi: ExtensionAPI): void {
|
|
|
94
69
|
if (runActive) return;
|
|
95
70
|
runActive = true;
|
|
96
71
|
runStartMs = Date.now();
|
|
97
|
-
runFailed = false;
|
|
98
|
-
runAborted = false;
|
|
99
|
-
thinkingSinceMs = undefined;
|
|
100
72
|
startTicker();
|
|
101
73
|
if (lastCtx?.hasUI) lastCtx.ui.setWorkingMessage("Working... 00:00");
|
|
102
74
|
});
|
|
103
75
|
|
|
104
|
-
pi.on("message_update", (event, ctx) => {
|
|
105
|
-
if (!runActive) return;
|
|
106
|
-
lastCtx = ctx;
|
|
107
|
-
const ev = event.assistantMessageEvent;
|
|
108
|
-
if (!ev) return;
|
|
109
|
-
const now = Date.now();
|
|
110
|
-
if (ev.type === "thinking_start") {
|
|
111
|
-
thinkingSinceMs = now;
|
|
112
|
-
if (lastCtx?.hasUI) {
|
|
113
|
-
lastCtx.ui.setHiddenThinkingLabel("Thinking... 00:00");
|
|
114
|
-
}
|
|
115
|
-
} else if (ev.type === "thinking_end" || ev.type === "text_start") {
|
|
116
|
-
endThinking();
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
pi.on("tool_execution_start", (_event, ctx) => {
|
|
121
|
-
if (!runActive) return;
|
|
122
|
-
lastCtx = ctx;
|
|
123
|
-
// 工具执行时恢复正常文案,避免 Thinking 计时残留
|
|
124
|
-
endThinking();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
pi.on("agent_end", (event, ctx) => {
|
|
128
|
-
if (!runActive) return;
|
|
129
|
-
lastCtx = ctx;
|
|
130
|
-
// 扫描消息找失败/中止信号,仅用于最终摘要素材
|
|
131
|
-
for (const msg of event.messages ?? []) {
|
|
132
|
-
if (msg.type === "assistant") {
|
|
133
|
-
if (msg.stopReason === "error" || msg.stopReason === "aborted") {
|
|
134
|
-
if (msg.stopReason === "error") runFailed = true;
|
|
135
|
-
else runAborted = true;
|
|
136
|
-
}
|
|
137
|
-
if (msg.errorMessage) runFailed = true;
|
|
138
|
-
} else if (msg.type === "toolResult" && msg.isError) {
|
|
139
|
-
runFailed = true;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
|
|
144
76
|
pi.on("agent_settled", (_event, ctx) => {
|
|
145
77
|
lastCtx = ctx;
|
|
146
78
|
if (!runActive) return;
|
|
147
|
-
endThinking();
|
|
148
79
|
appendFinal();
|
|
149
80
|
if (lastCtx?.hasUI) lastCtx.ui.setWorkingMessage(); // 恢复默认 "Working..."
|
|
150
81
|
runActive = false;
|
|
@@ -154,7 +85,6 @@ export function createStepTimer(pi: ExtensionAPI): void {
|
|
|
154
85
|
pi.on("session_shutdown", (_event, _ctx) => {
|
|
155
86
|
// 会话提前结束(如 /exit 或崩溃):尽力补一条最终汇总
|
|
156
87
|
if (runActive) {
|
|
157
|
-
endThinking();
|
|
158
88
|
appendFinal();
|
|
159
89
|
runActive = false;
|
|
160
90
|
}
|
|
@@ -162,16 +92,11 @@ export function createStepTimer(pi: ExtensionAPI): void {
|
|
|
162
92
|
lastCtx = undefined;
|
|
163
93
|
});
|
|
164
94
|
|
|
165
|
-
pi.registerEntryRenderer<FinalTimingData>(FINAL_TYPE, (entry, _opts,
|
|
95
|
+
pi.registerEntryRenderer<FinalTimingData>(FINAL_TYPE, (entry, _opts, _theme) => {
|
|
166
96
|
const d = entry.data;
|
|
167
97
|
if (!d) return undefined;
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
: d.aborted
|
|
171
|
-
? theme.fg("dim", "⏹ 任务中止 总耗时")
|
|
172
|
-
: theme.fg("accent", "✅ 任务完成 总耗时");
|
|
173
|
-
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
174
|
-
box.addChild(new Text(`${title} ${formatDuration(d.totalMs)}`, 0, 0));
|
|
98
|
+
const box = new Box(1, 1);
|
|
99
|
+
box.addChild(new Text(`总耗时 ${formatDuration(d.totalMs)}`, 0, 0));
|
|
175
100
|
return box;
|
|
176
101
|
});
|
|
177
102
|
}
|