@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 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.1 or newer (tested with the current Pi 0.84.3 release).
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hk_net/pi-timestamp",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Pi extension that shows timestamps for user input and agent completion timing",
5
5
  "type": "module",
6
6
  "private": false,
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
- let taskStartTime: number | undefined;
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
- taskStartTime ??= Date.now();
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 duration = endTime - startTime;
169
-
170
- ctx.ui.notify(`Done at ${formatTime(endTime)} · ${formatDuration(duration)}`, "info");
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) => {