@hk_net/pi-timestamp 0.1.12 → 0.1.13
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 +10 -0
- package/README.md +3 -3
- package/package.json +1 -1
- package/timestamp.ts +77 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.13 - 2026-08-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Split completion timing into total elapsed, active agent, and user-prompt waiting time when an extension UI prompt occurs during an agent run; retain the concise duration when none occurs.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Require Pi 0.84.4 or newer; this is the development, type-compatibility, and test baseline.
|
|
12
|
+
|
|
3
13
|
## 0.1.12 - 2026-08-27
|
|
4
14
|
|
|
5
15
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# pi-timestamp
|
|
2
2
|
|
|
3
|
-
Shows timestamps for user input and agent completion timing. Requires Pi 0.84.
|
|
3
|
+
Shows timestamps for user input and agent completion timing. Requires Pi 0.84.4 or newer.
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
7
|
- **User input**: Shows `Sent HH:MM:SS` as a dim status line in the chat display after each user message
|
|
8
|
-
- **Agent completion**: Shows `Done at HH:MM:SS · duration` as a dim status line in the chat display after each agent turn (e.g., `Done at 14:32:05 · 3.2s`)
|
|
8
|
+
- **Agent completion**: Shows `Done at HH:MM:SS · duration` as a dim status line in the chat display after each agent turn. If an extension UI prompt paused the run, it instead shows total, active-agent, and user-waiting time (e.g., `Done at 14:32:05 · total 8.2s · active 3.2s · waiting 5.0s`).
|
|
9
9
|
- **Session/runtime summaries**: Shows accent-colored summaries when switching sessions and at final Pi exit, including start/end times and durations. The final summary includes the complete Pi process runtime and every session interval.
|
|
10
10
|
|
|
11
11
|
All timestamps and summaries are **display-only** — session-switch summaries render in Pi's UI and the final runtime summary prints after Pi restores the terminal in TUI mode. They never enter the LLM context.
|
|
@@ -31,7 +31,7 @@ cp packages/pi-timestamp/timestamp.ts ~/.pi/agent/extensions/timestamp.ts
|
|
|
31
31
|
|
|
32
32
|
## Duration measurement
|
|
33
33
|
|
|
34
|
-
Captured from the first `agent_start` to `agent_settled`, so automatic retries, compaction recovery, tool calls, and queued continuations are included in one complete task duration.
|
|
34
|
+
Captured from the first `agent_start` to `agent_settled`, so automatic retries, compaction recovery, tool calls, and queued continuations are included in one complete task duration. When an extension opens a blocking UI prompt during that run, Pi 0.84.4's prompt events subtract that user-wait time from the reported active-agent duration while retaining it in the total.
|
|
35
35
|
|
|
36
36
|
## Issues and feedback
|
|
37
37
|
|
package/package.json
CHANGED
package/timestamp.ts
CHANGED
|
@@ -27,6 +27,19 @@ export interface TimestampRuntimeState {
|
|
|
27
27
|
pendingSessionSummary: SessionInterval | undefined;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export interface TaskTimingState {
|
|
31
|
+
startedAt: number | undefined;
|
|
32
|
+
promptStartedAt: number | undefined;
|
|
33
|
+
promptDepth: number;
|
|
34
|
+
waitingForUserMs: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CompletedTaskTiming {
|
|
38
|
+
totalMs: number;
|
|
39
|
+
activeMs: number;
|
|
40
|
+
waitingForUserMs: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
30
43
|
const RUNTIME_STATE_KEY = Symbol.for("hknet.pi-timestamp.runtime-state");
|
|
31
44
|
|
|
32
45
|
export function createRuntimeState(processStartedAt: number): TimestampRuntimeState {
|
|
@@ -67,6 +80,50 @@ export function takePendingSessionSummary(state: TimestampRuntimeState): Session
|
|
|
67
80
|
return pending;
|
|
68
81
|
}
|
|
69
82
|
|
|
83
|
+
export function createTaskTimingState(): TaskTimingState {
|
|
84
|
+
return { startedAt: undefined, promptStartedAt: undefined, promptDepth: 0, waitingForUserMs: 0 };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function beginTask(state: TaskTimingState, now: number): void {
|
|
88
|
+
if (state.startedAt !== undefined) return;
|
|
89
|
+
state.startedAt = now;
|
|
90
|
+
state.promptStartedAt = undefined;
|
|
91
|
+
state.promptDepth = 0;
|
|
92
|
+
state.waitingForUserMs = 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function beginUserPromptWait(state: TaskTimingState, now: number): void {
|
|
96
|
+
if (state.startedAt === undefined) return;
|
|
97
|
+
if (state.promptDepth++ === 0) state.promptStartedAt = now;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function endUserPromptWait(state: TaskTimingState, now: number): void {
|
|
101
|
+
if (state.promptDepth === 0) return;
|
|
102
|
+
if (--state.promptDepth !== 0 || state.promptStartedAt === undefined) return;
|
|
103
|
+
state.waitingForUserMs += Math.max(0, now - state.promptStartedAt);
|
|
104
|
+
state.promptStartedAt = undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function finishTask(state: TaskTimingState, now: number): CompletedTaskTiming | undefined {
|
|
108
|
+
if (state.startedAt === undefined) return undefined;
|
|
109
|
+
if (state.promptDepth > 0 && state.promptStartedAt !== undefined) {
|
|
110
|
+
state.waitingForUserMs += Math.max(0, now - state.promptStartedAt);
|
|
111
|
+
state.promptStartedAt = undefined;
|
|
112
|
+
state.promptDepth = 0;
|
|
113
|
+
}
|
|
114
|
+
const totalMs = Math.max(0, now - state.startedAt);
|
|
115
|
+
const completed = {
|
|
116
|
+
totalMs,
|
|
117
|
+
waitingForUserMs: Math.min(totalMs, state.waitingForUserMs),
|
|
118
|
+
activeMs: 0,
|
|
119
|
+
};
|
|
120
|
+
completed.activeMs = totalMs - completed.waitingForUserMs;
|
|
121
|
+
state.startedAt = undefined;
|
|
122
|
+
state.promptDepth = 0;
|
|
123
|
+
state.waitingForUserMs = 0;
|
|
124
|
+
return completed;
|
|
125
|
+
}
|
|
126
|
+
|
|
70
127
|
function formatTime(ts: number): string {
|
|
71
128
|
const d = new Date(ts);
|
|
72
129
|
const hh = String(d.getHours()).padStart(2, "0");
|
|
@@ -132,7 +189,7 @@ export function formatRuntimeDuration(ms: number): string {
|
|
|
132
189
|
}
|
|
133
190
|
|
|
134
191
|
export default function (pi: ExtensionAPI) {
|
|
135
|
-
|
|
192
|
+
const taskTiming = createTaskTimingState();
|
|
136
193
|
|
|
137
194
|
function notifyAccent(ctx: ExtensionContext, message: string): void {
|
|
138
195
|
ctx.ui.notify(ctx.ui.theme.fg("accent", message), "info");
|
|
@@ -141,7 +198,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
141
198
|
// Track the complete run. Automatic retries and compaction recovery can emit
|
|
142
199
|
// additional agent_start events before agent_settled, so retain the first start.
|
|
143
200
|
pi.on("agent_start", async () => {
|
|
144
|
-
|
|
201
|
+
beginTask(taskTiming, Date.now());
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Pi coalesces nested UI prompts. Track only prompt spans that overlap an
|
|
205
|
+
// active agent run, so configuration dialogs while idle do not affect timing.
|
|
206
|
+
pi.on("ui_prompt_start", async () => {
|
|
207
|
+
beginUserPromptWait(taskTiming, Date.now());
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
pi.on("ui_prompt_end", async () => {
|
|
211
|
+
endUserPromptWait(taskTiming, Date.now());
|
|
145
212
|
});
|
|
146
213
|
|
|
147
214
|
// Show "Sent HH:MM:SS" after each user message.
|
|
@@ -159,15 +226,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
159
226
|
// Show completion timing only after retries, compaction recovery, and queued
|
|
160
227
|
// continuations have fully settled.
|
|
161
228
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
162
|
-
const startTime = taskStartTime;
|
|
163
|
-
taskStartTime = undefined;
|
|
164
|
-
|
|
165
|
-
if (startTime === undefined) return;
|
|
166
|
-
|
|
167
229
|
const endTime = Date.now();
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
230
|
+
const timing = finishTask(taskTiming, endTime);
|
|
231
|
+
if (!timing) return;
|
|
232
|
+
|
|
233
|
+
const total = formatDuration(timing.totalMs);
|
|
234
|
+
const detail = timing.waitingForUserMs > 0
|
|
235
|
+
? ` · active ${formatDuration(timing.activeMs)} · waiting ${formatDuration(timing.waitingForUserMs)}`
|
|
236
|
+
: "";
|
|
237
|
+
ctx.ui.notify(`Done at ${formatTime(endTime)} · ${timing.waitingForUserMs > 0 ? `total ${total}` : total}${detail}`, "info");
|
|
171
238
|
});
|
|
172
239
|
|
|
173
240
|
pi.on("session_start", (_event, ctx) => {
|