pi-bar 0.3.29 → 0.3.30
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 +16 -14
- package/extensions/status-footer.ts +145 -114
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -3,25 +3,25 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/pi-bar)
|
|
4
4
|
[](https://www.npmjs.com/package/pi-bar)
|
|
5
5
|
|
|
6
|
-
**Never accidentally run Opus on a typo again.** pi-bar keeps your model, thinking level, context pressure, extension status, and a live one-line
|
|
6
|
+
**Never accidentally run Opus on a typo again.** pi-bar keeps your model, thinking level, context pressure, extension status, and a live one-line progress update visible in pi's footer.
|
|
7
7
|
|
|
8
8
|
```text
|
|
9
9
|
claude-opus-4.7 ❯ think:med ❯ 2.6% / 1.0M ❯ Inspecting package structure ❯ plan:active
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-

|
|
13
|
+

|
|
14
|
+

|
|
15
15
|
|
|
16
16
|
## Why use it?
|
|
17
17
|
|
|
18
18
|
- **See the active model at a glance** — catch accidental model switches before an expensive or sensitive task starts.
|
|
19
19
|
- **Track thinking level in place** — immediately notice when you are using the wrong reasoning setting.
|
|
20
20
|
- **Watch context pressure early** — context usage turns green, yellow, then red as you approach the limit.
|
|
21
|
-
- **Follow what pi is doing** — a live
|
|
21
|
+
- **Follow what pi is doing** — a live progress update keeps the current task visible without scrolling.
|
|
22
22
|
- **Keep extension statuses visible** — statuses set with `ctx.ui.setStatus()` still appear in the custom footer.
|
|
23
23
|
|
|
24
|
-
pi-bar is intentionally tiny: one small extension, no broad behavior changes, and no commands to learn. It replaces pi's built-in footer with a compact model / thinking / context /
|
|
24
|
+
pi-bar is intentionally tiny: one small extension, no broad behavior changes, and no commands to learn. It replaces pi's built-in footer with a compact model / thinking / context / progress / extension-status line.
|
|
25
25
|
|
|
26
26
|
## Install
|
|
27
27
|
|
|
@@ -42,23 +42,25 @@ pi-bar works out of the box, but you can tune it with environment variables befo
|
|
|
42
42
|
### Show or hide segments
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
PI_BAR_SHOW=model,thinking,context,
|
|
45
|
+
PI_BAR_SHOW=model,thinking,context,progress,extensions pi
|
|
46
46
|
PI_BAR_SHOW=model,context pi
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Allowed segments are `model`, `thinking`, `context`, `
|
|
49
|
+
Allowed segments are `model`, `thinking`, `context`, `progress`, and `extensions`. The `progress` segment is hidden until pi-bar generates a current update. The `extensions` segment is hidden when no extension has set a status.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
> The legacy `tldr` segment name and `PI_BAR_TLDR_MODEL` / `bar.tldrModel` settings still work and are kept as aliases for existing configs.
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
### Configure live progress updates
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
pi-bar shows a concise live progress update of the current task in the footer. It generates updates from recent prompt, assistant, and deterministic code-compressed tool facts (for example `bash ... ok`, `grep ... 4 matches`, `read file`, or `custom_tool key=value ok`). It resets when you navigate the session tree so stale summaries do not follow you across branches. Set `PI_BAR_SHOW` without `progress` to hide it.
|
|
56
|
+
|
|
57
|
+
Override the progress update model with:
|
|
56
58
|
|
|
57
59
|
```bash
|
|
58
|
-
|
|
60
|
+
PI_BAR_PROGRESS_MODEL=anthropic/claude-haiku-4-5 pi
|
|
59
61
|
```
|
|
60
62
|
|
|
61
|
-
You can also set `bar.
|
|
63
|
+
You can also set `bar.progressModel` in pi settings.
|
|
62
64
|
|
|
63
65
|
### Configure extension statuses
|
|
64
66
|
|
|
@@ -90,4 +92,4 @@ The first number is the warning/yellow threshold. The second number is the dange
|
|
|
90
92
|
|
|
91
93
|
Pi extensions run with your local user permissions. Review any pi package source before installing it.
|
|
92
94
|
|
|
93
|
-
Live
|
|
95
|
+
Live progress update generation sends short activity snippets to the selected provider. Disable the `progress` segment with `PI_BAR_SHOW` if that is unacceptable.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* pi-bar — footer/statusline extension.
|
|
3
3
|
*
|
|
4
4
|
* Replaces the built-in footer with left-aligned segments:
|
|
5
|
-
* <model name> ❯ think:<level> ❯ <context% / window> ❯ <
|
|
5
|
+
* <model name> ❯ think:<level> ❯ <context% / window> ❯ <progress> ❯ <extension statuses>
|
|
6
6
|
*
|
|
7
7
|
* Examples:
|
|
8
8
|
* claude-opus-4.7 ❯ think:med ❯ 2.6% / 1.0M
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* and after each assistant turn so context usage stays current.
|
|
12
12
|
*
|
|
13
13
|
* Configuration env vars:
|
|
14
|
-
* PI_BAR_SHOW=model,thinking,context,
|
|
14
|
+
* PI_BAR_SHOW=model,thinking,context,progress,extensions
|
|
15
15
|
* PI_BAR_THRESHOLDS=70,90
|
|
16
16
|
*/
|
|
17
17
|
|
|
@@ -33,39 +33,39 @@ import {
|
|
|
33
33
|
truncateToWidth,
|
|
34
34
|
} from "@earendil-works/pi-tui";
|
|
35
35
|
|
|
36
|
-
type SegmentName = "model" | "thinking" | "context" | "
|
|
36
|
+
type SegmentName = "model" | "thinking" | "context" | "progress" | "extensions";
|
|
37
37
|
type StatusFilter =
|
|
38
38
|
| { mode: "all"; hidden: Set<string> }
|
|
39
39
|
| { mode: "only"; shown: Set<string> };
|
|
40
40
|
type SerializedStatusFilter =
|
|
41
41
|
| { mode: "all"; hidden: string[] }
|
|
42
42
|
| { mode: "only"; shown: string[] };
|
|
43
|
-
type
|
|
43
|
+
type ProgressActivityType =
|
|
44
44
|
| "user_message"
|
|
45
45
|
| "assistant_update"
|
|
46
46
|
| "tool_call"
|
|
47
47
|
| "tool_result"
|
|
48
48
|
| "assistant_final"
|
|
49
49
|
| "assistant_failure";
|
|
50
|
-
type
|
|
51
|
-
type
|
|
50
|
+
type ProgressDisplayPriority = "immediate" | "normal" | "final";
|
|
51
|
+
type ProgressActivity = {
|
|
52
52
|
index: number;
|
|
53
|
-
activityType:
|
|
54
|
-
displayPriority:
|
|
53
|
+
activityType: ProgressActivityType;
|
|
54
|
+
displayPriority: ProgressDisplayPriority;
|
|
55
55
|
text: string;
|
|
56
56
|
toolCallId?: string;
|
|
57
57
|
};
|
|
58
|
-
type
|
|
58
|
+
type ProgressCheckpoint = {
|
|
59
59
|
activityIndex: number;
|
|
60
|
-
displayPriority:
|
|
60
|
+
displayPriority: ProgressDisplayPriority;
|
|
61
61
|
text: string;
|
|
62
62
|
};
|
|
63
|
-
type
|
|
63
|
+
type ProgressCheckpointJob = {
|
|
64
64
|
activityIndex: number;
|
|
65
|
-
displayPriority:
|
|
65
|
+
displayPriority: ProgressDisplayPriority;
|
|
66
66
|
runId: number;
|
|
67
67
|
};
|
|
68
|
-
type
|
|
68
|
+
type ProgressModelPreference = { provider: string; id: string };
|
|
69
69
|
type FastModelAuth = {
|
|
70
70
|
model: Parameters<typeof complete>[0];
|
|
71
71
|
apiKey: string;
|
|
@@ -73,7 +73,10 @@ type FastModelAuth = {
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
const STATUS_FILTER_ENTRY_TYPE = "pi-bar-status-filter";
|
|
76
|
-
const
|
|
76
|
+
const SETTINGS_PROGRESS_KEY = "progress";
|
|
77
|
+
// Legacy settings/env names. The `tldr` keys remain accepted so existing user
|
|
78
|
+
// configs continue to work after the rename to "progress updates".
|
|
79
|
+
const LEGACY_SETTINGS_TLDR_KEY = "tldr";
|
|
77
80
|
const SETTINGS_BAR_KEY = "bar";
|
|
78
81
|
const MAX_ACTIVITY_TEXT_CHARS = 800;
|
|
79
82
|
const MAX_USER_TEXT_CHARS = 700;
|
|
@@ -84,10 +87,10 @@ const MAX_FINAL_TEXT_CHARS = 700;
|
|
|
84
87
|
const MAX_RETAINED_RAW_ACTIVITIES = 128;
|
|
85
88
|
const ANSI_PATTERN = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
86
89
|
const MAX_CONTEXT_CHECKPOINTS = 8;
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
const
|
|
90
|
+
const PROGRESS_MAX_TOKENS = 120;
|
|
91
|
+
const PROGRESS_REQUEST_TIMEOUT_MS = 2_000;
|
|
92
|
+
const PROGRESS_DISPLAY_UPDATE_INTERVAL_MS = 1_200;
|
|
93
|
+
const PROGRESS_TARGET_SUMMARY_CHARS = 60;
|
|
91
94
|
// Burst debouncing on the generation side: coalesce rapid-fire activities into
|
|
92
95
|
// one model call. Quiet window catches short bursts; max wait surfaces progress
|
|
93
96
|
// during continuous activity.
|
|
@@ -97,7 +100,7 @@ const NORMAL_CHECKPOINT_QUIET_MS = 1_500;
|
|
|
97
100
|
const NORMAL_CHECKPOINT_MAX_WAIT_MS = 2_500;
|
|
98
101
|
// Defensive ceiling: even if the model ignores the length instruction, never
|
|
99
102
|
// blast a large payload into the footer.
|
|
100
|
-
const
|
|
103
|
+
const MAX_SAFE_PROGRESS_CHARS = 240;
|
|
101
104
|
const CONFIG_PATH =
|
|
102
105
|
process.env.PI_BAR_CONFIG ?? join(homedir(), ".pi", "agent", "pi-bar.json");
|
|
103
106
|
|
|
@@ -105,7 +108,7 @@ const DEFAULT_SEGMENTS: SegmentName[] = [
|
|
|
105
108
|
"model",
|
|
106
109
|
"thinking",
|
|
107
110
|
"context",
|
|
108
|
-
"
|
|
111
|
+
"progress",
|
|
109
112
|
"extensions",
|
|
110
113
|
];
|
|
111
114
|
const DEFAULT_WARNING_THRESHOLD = 70;
|
|
@@ -168,11 +171,14 @@ function parseSegments(): SegmentName[] {
|
|
|
168
171
|
const raw = process.env.PI_BAR_SHOW;
|
|
169
172
|
if (!raw) return DEFAULT_SEGMENTS;
|
|
170
173
|
|
|
174
|
+
// `tldr` is the legacy segment name, kept working as an alias so existing
|
|
175
|
+
// PI_BAR_SHOW configs do not break after the rename to "progress".
|
|
171
176
|
const requested = raw
|
|
172
177
|
.split(",")
|
|
173
178
|
.map((segment) => segment.trim().toLowerCase())
|
|
179
|
+
.map((segment) => (segment === "tldr" ? "progress" : segment))
|
|
174
180
|
.filter((segment): segment is SegmentName =>
|
|
175
|
-
["model", "thinking", "context", "
|
|
181
|
+
["model", "thinking", "context", "progress", "extensions"].includes(segment),
|
|
176
182
|
);
|
|
177
183
|
|
|
178
184
|
return requested.length > 0 ? requested : DEFAULT_SEGMENTS;
|
|
@@ -206,7 +212,7 @@ function parseThresholds(): { warningThreshold: number; errorThreshold: number }
|
|
|
206
212
|
};
|
|
207
213
|
}
|
|
208
214
|
|
|
209
|
-
function
|
|
215
|
+
function parseProgressModelSpec(value: string): ProgressModelPreference | undefined {
|
|
210
216
|
const trimmed = value.trim();
|
|
211
217
|
if (!trimmed || trimmed === "auto") return undefined;
|
|
212
218
|
const separator = trimmed.indexOf("/");
|
|
@@ -217,51 +223,64 @@ function parseTldrModelSpec(value: string): TldrModelPreference | undefined {
|
|
|
217
223
|
function settingsModelValue(settings: Record<string, unknown>): string | undefined {
|
|
218
224
|
const bar = settings[SETTINGS_BAR_KEY];
|
|
219
225
|
if (bar && typeof bar === "object" && !Array.isArray(bar)) {
|
|
220
|
-
const
|
|
226
|
+
const record = bar as Record<string, unknown>;
|
|
227
|
+
// Canonical key after rename. Falls through to legacy `tldrModel` so old
|
|
228
|
+
// settings still work without manual migration.
|
|
229
|
+
if (typeof record.progressModel === "string") return record.progressModel;
|
|
230
|
+
if (typeof record.tldrModel === "string") return record.tldrModel;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const progressSection = settings[SETTINGS_PROGRESS_KEY];
|
|
234
|
+
if (progressSection && typeof progressSection === "object" && !Array.isArray(progressSection)) {
|
|
235
|
+
const value = (progressSection as Record<string, unknown>).model;
|
|
221
236
|
if (typeof value === "string") return value;
|
|
222
237
|
}
|
|
223
238
|
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
const value = (
|
|
239
|
+
const legacy = settings[LEGACY_SETTINGS_TLDR_KEY];
|
|
240
|
+
if (legacy && typeof legacy === "object" && !Array.isArray(legacy)) {
|
|
241
|
+
const value = (legacy as Record<string, unknown>).model;
|
|
227
242
|
if (typeof value === "string") return value;
|
|
228
243
|
}
|
|
229
244
|
|
|
230
245
|
return undefined;
|
|
231
246
|
}
|
|
232
247
|
|
|
233
|
-
function
|
|
234
|
-
|
|
235
|
-
|
|
248
|
+
function resolveProgressModelPreference(cwd: string): ProgressModelPreference | undefined {
|
|
249
|
+
// PI_BAR_PROGRESS_MODEL is the canonical env var. PI_BAR_TLDR_MODEL is
|
|
250
|
+
// honored as a backwards-compat alias for users who set it before the
|
|
251
|
+
// rename.
|
|
252
|
+
const envModel = process.env.PI_BAR_PROGRESS_MODEL ?? process.env.PI_BAR_TLDR_MODEL;
|
|
253
|
+
if (envModel) return parseProgressModelSpec(envModel);
|
|
236
254
|
|
|
237
255
|
const settings = SettingsManager.create(cwd);
|
|
238
256
|
const projectModel = settingsModelValue(
|
|
239
257
|
settings.getProjectSettings() as Record<string, unknown>,
|
|
240
258
|
);
|
|
241
|
-
if (projectModel !== undefined) return
|
|
259
|
+
if (projectModel !== undefined) return parseProgressModelSpec(projectModel);
|
|
242
260
|
|
|
243
261
|
const globalModel = settingsModelValue(
|
|
244
262
|
settings.getGlobalSettings() as Record<string, unknown>,
|
|
245
263
|
);
|
|
246
|
-
return globalModel ?
|
|
264
|
+
return globalModel ? parseProgressModelSpec(globalModel) : undefined;
|
|
247
265
|
}
|
|
248
266
|
|
|
249
|
-
// Fallback order optimizes for low latency + low cost first. The
|
|
250
|
-
// not need reasoning quality; codex/gpt-5.4-mini is fastest
|
|
251
|
-
|
|
267
|
+
// Fallback order optimizes for low latency + low cost first. The progress
|
|
268
|
+
// update path does not need reasoning quality; codex/gpt-5.4-mini is fastest
|
|
269
|
+
// in practice.
|
|
270
|
+
const FAST_PROGRESS_MODELS: readonly ProgressModelPreference[] = [
|
|
252
271
|
{ provider: "openai-codex", id: "gpt-5.4-mini" },
|
|
253
272
|
{ provider: "openai-codex", id: "gpt-5.3-codex-spark" },
|
|
254
273
|
{ provider: "anthropic", id: "claude-haiku-4-5" },
|
|
255
274
|
{ provider: "anthropic", id: "claude-haiku-4-5-20251001" },
|
|
256
275
|
];
|
|
257
276
|
|
|
258
|
-
function
|
|
277
|
+
function formatProgressModelKey(model: ProgressModelPreference): string {
|
|
259
278
|
return `${model.provider}/${model.id}`;
|
|
260
279
|
}
|
|
261
280
|
|
|
262
281
|
async function getModelAuth(
|
|
263
282
|
ctx: ExtensionContext,
|
|
264
|
-
preference:
|
|
283
|
+
preference: ProgressModelPreference,
|
|
265
284
|
): Promise<FastModelAuth | undefined> {
|
|
266
285
|
const model = ctx.modelRegistry.find(preference.provider, preference.id);
|
|
267
286
|
if (!model) return undefined;
|
|
@@ -272,17 +291,17 @@ async function getModelAuth(
|
|
|
272
291
|
: undefined;
|
|
273
292
|
}
|
|
274
293
|
|
|
275
|
-
async function
|
|
294
|
+
async function getFastProgressModelAuth(
|
|
276
295
|
ctx: ExtensionContext,
|
|
277
|
-
configuredModel?:
|
|
296
|
+
configuredModel?: ProgressModelPreference,
|
|
278
297
|
): Promise<FastModelAuth | undefined> {
|
|
279
298
|
if (configuredModel) {
|
|
280
299
|
const configuredAuth = await getModelAuth(ctx, configuredModel);
|
|
281
300
|
if (configuredAuth) return configuredAuth;
|
|
282
301
|
}
|
|
283
302
|
|
|
284
|
-
for (const candidate of
|
|
285
|
-
if (configuredModel &&
|
|
303
|
+
for (const candidate of FAST_PROGRESS_MODELS) {
|
|
304
|
+
if (configuredModel && formatProgressModelKey(candidate) === formatProgressModelKey(configuredModel)) {
|
|
286
305
|
continue;
|
|
287
306
|
}
|
|
288
307
|
const auth = await getModelAuth(ctx, candidate);
|
|
@@ -450,16 +469,16 @@ function extractTextContent(content: readonly unknown[] | undefined): string | u
|
|
|
450
469
|
return compacted.length > 0 ? compacted : undefined;
|
|
451
470
|
}
|
|
452
471
|
|
|
453
|
-
class
|
|
472
|
+
class ProgressFactCollector {
|
|
454
473
|
private nextIndex = 1;
|
|
455
|
-
private readonly activities:
|
|
474
|
+
private readonly activities: ProgressActivity[] = [];
|
|
456
475
|
|
|
457
476
|
resetConversation(): void {
|
|
458
477
|
this.nextIndex = 1;
|
|
459
478
|
this.activities.splice(0);
|
|
460
479
|
}
|
|
461
480
|
|
|
462
|
-
recordUserMessage(prompt: string):
|
|
481
|
+
recordUserMessage(prompt: string): ProgressActivity {
|
|
463
482
|
return this.addActivity(
|
|
464
483
|
"user_message",
|
|
465
484
|
"immediate",
|
|
@@ -467,7 +486,7 @@ class TldrFactCollector {
|
|
|
467
486
|
);
|
|
468
487
|
}
|
|
469
488
|
|
|
470
|
-
recordAssistantUpdate(message: unknown):
|
|
489
|
+
recordAssistantUpdate(message: unknown): ProgressActivity | undefined {
|
|
471
490
|
if (!message || typeof message !== "object") return undefined;
|
|
472
491
|
const record = message as Record<string, unknown>;
|
|
473
492
|
if (record.role !== "assistant") return undefined;
|
|
@@ -492,7 +511,7 @@ class TldrFactCollector {
|
|
|
492
511
|
toolName: string;
|
|
493
512
|
input?: Record<string, unknown>;
|
|
494
513
|
toolCallId?: string;
|
|
495
|
-
}):
|
|
514
|
+
}): ProgressActivity {
|
|
496
515
|
return this.addActivity(
|
|
497
516
|
"tool_call",
|
|
498
517
|
"normal",
|
|
@@ -508,7 +527,7 @@ class TldrFactCollector {
|
|
|
508
527
|
content?: readonly unknown[];
|
|
509
528
|
details?: unknown;
|
|
510
529
|
toolCallId?: string;
|
|
511
|
-
}):
|
|
530
|
+
}): ProgressActivity {
|
|
512
531
|
const isError = Boolean(event.isError);
|
|
513
532
|
const resultText = summarizeToolResult(
|
|
514
533
|
event.toolName,
|
|
@@ -540,7 +559,7 @@ class TldrFactCollector {
|
|
|
540
559
|
);
|
|
541
560
|
}
|
|
542
561
|
|
|
543
|
-
recordMessageEnd(message: unknown):
|
|
562
|
+
recordMessageEnd(message: unknown): ProgressActivity | "emptyFinalStop" | "ignored" {
|
|
544
563
|
if (!message || typeof message !== "object") return "ignored";
|
|
545
564
|
const record = message as Record<string, unknown>;
|
|
546
565
|
if (record.role !== "assistant") return "ignored";
|
|
@@ -569,7 +588,7 @@ class TldrFactCollector {
|
|
|
569
588
|
: "ignored";
|
|
570
589
|
}
|
|
571
590
|
|
|
572
|
-
activitiesAfter(previousIndex: number, throughIndex: number): readonly
|
|
591
|
+
activitiesAfter(previousIndex: number, throughIndex: number): readonly ProgressActivity[] {
|
|
573
592
|
return this.activities.filter(
|
|
574
593
|
(activity) => activity.index > previousIndex && activity.index <= throughIndex,
|
|
575
594
|
);
|
|
@@ -591,11 +610,11 @@ class TldrFactCollector {
|
|
|
591
610
|
}
|
|
592
611
|
|
|
593
612
|
private addActivity(
|
|
594
|
-
activityType:
|
|
595
|
-
displayPriority:
|
|
613
|
+
activityType: ProgressActivityType,
|
|
614
|
+
displayPriority: ProgressDisplayPriority,
|
|
596
615
|
text: string,
|
|
597
616
|
toolCallId?: string,
|
|
598
|
-
):
|
|
617
|
+
): ProgressActivity {
|
|
599
618
|
const activity = {
|
|
600
619
|
index: this.nextIndex,
|
|
601
620
|
activityType,
|
|
@@ -612,25 +631,25 @@ class TldrFactCollector {
|
|
|
612
631
|
}
|
|
613
632
|
}
|
|
614
633
|
|
|
615
|
-
class
|
|
616
|
-
private readonly facts = new
|
|
617
|
-
private configuredModel?:
|
|
634
|
+
class FooterProgressEngine {
|
|
635
|
+
private readonly facts = new ProgressFactCollector();
|
|
636
|
+
private configuredModel?: ProgressModelPreference;
|
|
618
637
|
private runId = 0;
|
|
619
638
|
private latestAcceptedActivityIndex = 0;
|
|
620
639
|
private lastRenderedActivityIndex = 0;
|
|
621
640
|
private lastRenderedText = "";
|
|
622
641
|
private lastDisplayAt = Number.NEGATIVE_INFINITY;
|
|
623
|
-
private readonly acceptedCheckpoints:
|
|
624
|
-
private checkpointQueue:
|
|
625
|
-
private inFlightCheckpoint?:
|
|
626
|
-
private pendingDisplayCheckpoint?:
|
|
642
|
+
private readonly acceptedCheckpoints: ProgressCheckpoint[] = [];
|
|
643
|
+
private checkpointQueue: ProgressCheckpointJob[] = [];
|
|
644
|
+
private inFlightCheckpoint?: ProgressCheckpointJob;
|
|
645
|
+
private pendingDisplayCheckpoint?: ProgressCheckpoint;
|
|
627
646
|
private displayTimer?: ReturnType<typeof setTimeout>;
|
|
628
647
|
private abortController?: AbortController;
|
|
629
648
|
private currentText: string | null = null;
|
|
630
649
|
// Normal-priority bursts wait for a quiet window before triggering a model
|
|
631
650
|
// call. Pi often emits many rapid activities (e.g. streaming reads); without
|
|
632
|
-
// debouncing we would hammer the
|
|
633
|
-
private pendingNormalCheckpoint?:
|
|
651
|
+
// debouncing we would hammer the progress update model for intermediate states.
|
|
652
|
+
private pendingNormalCheckpoint?: ProgressCheckpointJob;
|
|
634
653
|
private normalCheckpointTimer?: ReturnType<typeof setTimeout>;
|
|
635
654
|
private normalCheckpointBurstStartedAt?: number;
|
|
636
655
|
|
|
@@ -641,7 +660,7 @@ class FooterTldrEngine {
|
|
|
641
660
|
}
|
|
642
661
|
|
|
643
662
|
startSession(cwd: string): void {
|
|
644
|
-
this.configuredModel =
|
|
663
|
+
this.configuredModel = resolveProgressModelPreference(cwd);
|
|
645
664
|
this.facts.resetConversation();
|
|
646
665
|
this.startFreshRun();
|
|
647
666
|
}
|
|
@@ -652,7 +671,7 @@ class FooterTldrEngine {
|
|
|
652
671
|
}
|
|
653
672
|
|
|
654
673
|
recordUserMessage(ctx: ExtensionContext, prompt: string): void {
|
|
655
|
-
// A new user turn replaces the entire visible
|
|
674
|
+
// A new user turn replaces the entire visible progress update context, so wipe both
|
|
656
675
|
// the rendered text and the prior-turn checkpoint context that feeds the
|
|
657
676
|
// model. Carrying old checkpoints biased the next turn's phrasing toward
|
|
658
677
|
// the previous task.
|
|
@@ -766,7 +785,7 @@ class FooterTldrEngine {
|
|
|
766
785
|
|
|
767
786
|
private scheduleNormalCheckpoint(
|
|
768
787
|
ctx: ExtensionContext,
|
|
769
|
-
job:
|
|
788
|
+
job: ProgressCheckpointJob,
|
|
770
789
|
): void {
|
|
771
790
|
this.pendingNormalCheckpoint = job;
|
|
772
791
|
this.normalCheckpointBurstStartedAt ??= performance.now();
|
|
@@ -804,7 +823,7 @@ class FooterTldrEngine {
|
|
|
804
823
|
this.pump(ctx);
|
|
805
824
|
}
|
|
806
825
|
|
|
807
|
-
private enqueue(ctx: ExtensionContext, activity:
|
|
826
|
+
private enqueue(ctx: ExtensionContext, activity: ProgressActivity): void {
|
|
808
827
|
if (!ctx.hasUI) return;
|
|
809
828
|
const job = {
|
|
810
829
|
activityIndex: activity.index,
|
|
@@ -868,19 +887,19 @@ class FooterTldrEngine {
|
|
|
868
887
|
void this.runCheckpointRequest(ctx, job);
|
|
869
888
|
}
|
|
870
889
|
|
|
871
|
-
private isCurrentJob(job:
|
|
890
|
+
private isCurrentJob(job: ProgressCheckpointJob): boolean {
|
|
872
891
|
return job.runId === this.runId && this.inFlightCheckpoint === job;
|
|
873
892
|
}
|
|
874
893
|
|
|
875
894
|
private async runCheckpointRequest(
|
|
876
895
|
ctx: ExtensionContext,
|
|
877
|
-
job:
|
|
896
|
+
job: ProgressCheckpointJob,
|
|
878
897
|
): Promise<void> {
|
|
879
898
|
let abortController: AbortController | undefined;
|
|
880
899
|
try {
|
|
881
900
|
const prompt = this.checkpointPrompt(job);
|
|
882
901
|
if (!prompt) return;
|
|
883
|
-
const auth = await
|
|
902
|
+
const auth = await getFastProgressModelAuth(ctx, this.configuredModel);
|
|
884
903
|
if (!this.isCurrentJob(job) || !auth) return;
|
|
885
904
|
|
|
886
905
|
abortController = new AbortController();
|
|
@@ -891,17 +910,17 @@ class FooterTldrEngine {
|
|
|
891
910
|
{
|
|
892
911
|
apiKey: auth.apiKey,
|
|
893
912
|
headers: auth.headers,
|
|
894
|
-
maxTokens:
|
|
913
|
+
maxTokens: PROGRESS_MAX_TOKENS,
|
|
895
914
|
maxRetries: 0,
|
|
896
915
|
cacheRetention: "none",
|
|
897
|
-
timeoutMs:
|
|
916
|
+
timeoutMs: PROGRESS_REQUEST_TIMEOUT_MS,
|
|
898
917
|
signal: abortController.signal,
|
|
899
918
|
},
|
|
900
919
|
);
|
|
901
920
|
if (!this.isCurrentJob(job) || response.stopReason !== "stop") return;
|
|
902
921
|
|
|
903
922
|
const rawText = extractTextContent(response.content) ?? "";
|
|
904
|
-
const text =
|
|
923
|
+
const text = sanitizeProgressText(rawText);
|
|
905
924
|
if (!text) return;
|
|
906
925
|
const checkpoint = {
|
|
907
926
|
activityIndex: job.activityIndex,
|
|
@@ -921,7 +940,7 @@ class FooterTldrEngine {
|
|
|
921
940
|
}
|
|
922
941
|
}
|
|
923
942
|
|
|
924
|
-
private acceptCheckpoint(checkpoint:
|
|
943
|
+
private acceptCheckpoint(checkpoint: ProgressCheckpoint): void {
|
|
925
944
|
this.acceptedCheckpoints.push(checkpoint);
|
|
926
945
|
if (this.acceptedCheckpoints.length > MAX_CONTEXT_CHECKPOINTS) {
|
|
927
946
|
this.acceptedCheckpoints.splice(
|
|
@@ -935,7 +954,7 @@ class FooterTldrEngine {
|
|
|
935
954
|
|
|
936
955
|
private considerDisplayingCheckpoint(
|
|
937
956
|
ctx: ExtensionContext,
|
|
938
|
-
checkpoint:
|
|
957
|
+
checkpoint: ProgressCheckpoint,
|
|
939
958
|
): void {
|
|
940
959
|
if (checkpoint.activityIndex <= this.lastRenderedActivityIndex) return;
|
|
941
960
|
if (checkpoint.activityIndex !== this.facts.latestActivityIndex()) return;
|
|
@@ -947,7 +966,7 @@ class FooterTldrEngine {
|
|
|
947
966
|
}
|
|
948
967
|
|
|
949
968
|
const elapsedMs = performance.now() - this.lastDisplayAt;
|
|
950
|
-
if (elapsedMs >=
|
|
969
|
+
if (elapsedMs >= PROGRESS_DISPLAY_UPDATE_INTERVAL_MS) {
|
|
951
970
|
this.clearPendingDisplay();
|
|
952
971
|
this.renderCheckpoint(checkpoint);
|
|
953
972
|
return;
|
|
@@ -961,16 +980,16 @@ class FooterTldrEngine {
|
|
|
961
980
|
this.pendingDisplayCheckpoint = undefined;
|
|
962
981
|
if (!pending || pending.activityIndex !== this.facts.latestActivityIndex()) return;
|
|
963
982
|
this.renderCheckpoint(pending);
|
|
964
|
-
},
|
|
983
|
+
}, PROGRESS_DISPLAY_UPDATE_INTERVAL_MS - elapsedMs);
|
|
965
984
|
}
|
|
966
985
|
|
|
967
|
-
private renderCheckpoint(checkpoint:
|
|
986
|
+
private renderCheckpoint(checkpoint: ProgressCheckpoint): void {
|
|
968
987
|
if (checkpoint.activityIndex <= this.lastRenderedActivityIndex) return;
|
|
969
988
|
if (checkpoint.text === this.lastRenderedText) return;
|
|
970
989
|
// Skip near-duplicates: a burst of consecutive checkpoints often produces
|
|
971
990
|
// the same action fragment with only punctuation/casing differences. They
|
|
972
991
|
// flash visibly in the footer without adding information.
|
|
973
|
-
if (
|
|
992
|
+
if (isNearDuplicateProgress(checkpoint.text, this.lastRenderedText)) {
|
|
974
993
|
this.lastRenderedActivityIndex = checkpoint.activityIndex;
|
|
975
994
|
return;
|
|
976
995
|
}
|
|
@@ -981,7 +1000,7 @@ class FooterTldrEngine {
|
|
|
981
1000
|
this.requestRender();
|
|
982
1001
|
}
|
|
983
1002
|
|
|
984
|
-
private checkpointPrompt(job:
|
|
1003
|
+
private checkpointPrompt(job: ProgressCheckpointJob): UserMessage | undefined {
|
|
985
1004
|
const rawActivities = this.facts.activitiesAfter(
|
|
986
1005
|
this.latestAcceptedActivityIndex,
|
|
987
1006
|
job.activityIndex,
|
|
@@ -993,13 +1012,13 @@ class FooterTldrEngine {
|
|
|
993
1012
|
{
|
|
994
1013
|
type: "text",
|
|
995
1014
|
text: [
|
|
996
|
-
"Prior
|
|
1015
|
+
"Prior progress updates (context only, do not copy phrasing):",
|
|
997
1016
|
previousCheckpointLines(this.acceptedCheckpoints),
|
|
998
1017
|
"",
|
|
999
1018
|
"New activity to summarize:",
|
|
1000
1019
|
...rawActivities.map(formatRawActivity),
|
|
1001
1020
|
"",
|
|
1002
|
-
"Write the next
|
|
1021
|
+
"Write the next progress update.",
|
|
1003
1022
|
].join("\n"),
|
|
1004
1023
|
},
|
|
1005
1024
|
],
|
|
@@ -1090,11 +1109,12 @@ const BANNED_FIRST_WORDS = [
|
|
|
1090
1109
|
"Find",
|
|
1091
1110
|
] as const;
|
|
1092
1111
|
|
|
1093
|
-
function checkpointSystemPrompt(job:
|
|
1094
|
-
// Tense + exemplars vary by priority. Final
|
|
1095
|
-
// using -ing examples for final caused all
|
|
1096
|
-
// present-progressive even though the literal instruction said
|
|
1097
|
-
//
|
|
1112
|
+
function checkpointSystemPrompt(job: ProgressCheckpointJob): string {
|
|
1113
|
+
// Tense + exemplars vary by priority. Final progress updates describe
|
|
1114
|
+
// completed turns; using -ing examples for final caused all of them to slip
|
|
1115
|
+
// back into present-progressive even though the literal instruction said
|
|
1116
|
+
// past tense.
|
|
1117
|
+
// Immediate progress updates cover a brand-new user request; they need a rephrase
|
|
1098
1118
|
// directive instead of generic filler like "Continuing with task".
|
|
1099
1119
|
let tenseInstruction: string;
|
|
1100
1120
|
let goodExamples: string;
|
|
@@ -1103,7 +1123,7 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
|
|
|
1103
1123
|
tenseInstruction = "Start with a past-tense verb describing what was completed.";
|
|
1104
1124
|
goodExamples = [
|
|
1105
1125
|
"- Updated footer summary behavior",
|
|
1106
|
-
"- Investigated live
|
|
1126
|
+
"- Investigated live progress regressions",
|
|
1107
1127
|
"- Refined sanitizer for stray prefixes",
|
|
1108
1128
|
"- Wrapped up extension release",
|
|
1109
1129
|
].join("\n");
|
|
@@ -1113,7 +1133,7 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
|
|
|
1113
1133
|
"Rephrase the user's new request as a concise present-progressive task clause. If the request is opaque (e.g. \"continue\", \"go\", \"ok\"), name the carry-over task with a noun (e.g. \"Resuming refactor work\"), not generic filler.";
|
|
1114
1134
|
goodExamples = [
|
|
1115
1135
|
"- Reviewing footer summary behavior",
|
|
1116
|
-
"- Investigating live
|
|
1136
|
+
"- Investigating live progress regressions",
|
|
1117
1137
|
"- Refining sanitizer for stray prefixes",
|
|
1118
1138
|
"- Preparing extension release",
|
|
1119
1139
|
"- Resuming refactor work",
|
|
@@ -1123,7 +1143,7 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
|
|
|
1123
1143
|
tenseInstruction = "Start with a present-tense -ing verb describing current work.";
|
|
1124
1144
|
goodExamples = [
|
|
1125
1145
|
"- Reviewing footer summary behavior",
|
|
1126
|
-
"- Investigating live
|
|
1146
|
+
"- Investigating live progress regressions",
|
|
1127
1147
|
"- Refining sanitizer for stray prefixes",
|
|
1128
1148
|
"- Wrapping up extension release",
|
|
1129
1149
|
].join("\n");
|
|
@@ -1136,7 +1156,7 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
|
|
|
1136
1156
|
// suffixes, file path leaks, and markdown formatting. The HARD CONSTRAINTS
|
|
1137
1157
|
// block sits at the tail of the prompt because instruction-following models
|
|
1138
1158
|
// give the most weight to the most-recent directives (recency bias).
|
|
1139
|
-
return `Write one plain-English
|
|
1159
|
+
return `Write one plain-English progress update for a Pi coding agent.
|
|
1140
1160
|
Describe the work progress as if a human developer were doing it.
|
|
1141
1161
|
Focus on the task activity and current outcome, not agent mechanics.
|
|
1142
1162
|
Do not mention tools, tool calls, prompts, messages, model output, or implementation details.
|
|
@@ -1145,11 +1165,11 @@ Do not use file paths, file extensions, code identifiers, package names, or vers
|
|
|
1145
1165
|
Do not use backticks, asterisks, underscores, quotes, or any markdown formatting.
|
|
1146
1166
|
Do not append filler suffixes such as "with success", "successfully", or "completed successfully".
|
|
1147
1167
|
Do not claim progress or completion that is not present in the activity.
|
|
1148
|
-
Use the prior
|
|
1168
|
+
Use the prior progress updates for context and the new activity for the update.
|
|
1149
1169
|
Summarize the current state of work; do not narrate the history.
|
|
1150
1170
|
If context is sparse, still summarize the available activity.
|
|
1151
1171
|
Never ask for more information or say there is not enough context.
|
|
1152
|
-
Return one concise status fragment under ${
|
|
1172
|
+
Return one concise status fragment under ${PROGRESS_TARGET_SUMMARY_CHARS} characters.
|
|
1153
1173
|
Omit subjects like "the agent" or "it".
|
|
1154
1174
|
Prefer verb + direct object. Include outcome only if important.
|
|
1155
1175
|
Do not address the user.
|
|
@@ -1163,7 +1183,7 @@ Bad examples:
|
|
|
1163
1183
|
- Editing extensions/status-footer.ts with success.
|
|
1164
1184
|
- Reading status-footer file completed successfully.
|
|
1165
1185
|
- Publishing \`pi-bar@0.3.3\` to npm.
|
|
1166
|
-
- Grepping for
|
|
1186
|
+
- Grepping for sanitizeProgressText callers.
|
|
1167
1187
|
- Verifying repository status after commit.
|
|
1168
1188
|
- Investigating user input responses.
|
|
1169
1189
|
|
|
@@ -1180,7 +1200,7 @@ function extractStopReason(message: unknown): string | undefined {
|
|
|
1180
1200
|
return typeof stopReason === "string" ? stopReason : undefined;
|
|
1181
1201
|
}
|
|
1182
1202
|
|
|
1183
|
-
// Two
|
|
1203
|
+
// Two progress updates are near-duplicates if their normalized action fragments (lowercase,
|
|
1184
1204
|
// punctuation/whitespace collapsed) match. Catches bursts of cosmetic variants
|
|
1185
1205
|
// like "Editing X." vs "Editing X" vs "editing x.".
|
|
1186
1206
|
function normalizedActionFragment(text: string): string {
|
|
@@ -1191,25 +1211,25 @@ function normalizedActionFragment(text: string): string {
|
|
|
1191
1211
|
.trim();
|
|
1192
1212
|
}
|
|
1193
1213
|
|
|
1194
|
-
function
|
|
1214
|
+
function isNearDuplicateProgress(current: string, previous: string): boolean {
|
|
1195
1215
|
if (!previous) return false;
|
|
1196
1216
|
return normalizedActionFragment(current) === normalizedActionFragment(previous);
|
|
1197
1217
|
}
|
|
1198
1218
|
|
|
1199
|
-
function previousCheckpointLines(checkpoints: readonly
|
|
1219
|
+
function previousCheckpointLines(checkpoints: readonly ProgressCheckpoint[]): string {
|
|
1200
1220
|
if (checkpoints.length === 0) return "none";
|
|
1201
1221
|
return checkpoints
|
|
1202
1222
|
.slice(-MAX_CONTEXT_CHECKPOINTS)
|
|
1203
|
-
.map((checkpoint) => `- ${
|
|
1223
|
+
.map((checkpoint) => `- ${sanitizeProgressText(checkpoint.text)}`)
|
|
1204
1224
|
.join("\n");
|
|
1205
1225
|
}
|
|
1206
1226
|
|
|
1207
|
-
function formatRawActivity(activity:
|
|
1227
|
+
function formatRawActivity(activity: ProgressActivity): string {
|
|
1208
1228
|
return `- ${activity.text}`;
|
|
1209
1229
|
}
|
|
1210
1230
|
|
|
1211
1231
|
// Strips ANSI/CSI/OSC/DCS/SOS/PM/APC escape sequences from model output before
|
|
1212
|
-
// it lands in the terminal. The
|
|
1232
|
+
// it lands in the terminal. The progress update text is model-controlled and renders into
|
|
1213
1233
|
// the footer; the system prompt asking for plain text is not a security
|
|
1214
1234
|
// boundary, so we defensively remove terminal controls here.
|
|
1215
1235
|
const ESC_BYTE = 0x1b;
|
|
@@ -1315,7 +1335,7 @@ function stripTerminalControls(text: string): string {
|
|
|
1315
1335
|
// parrot them; the regex is narrow enough not to clip natural starts like
|
|
1316
1336
|
// "Activity slowed after retry" or "Checkpointing release state".
|
|
1317
1337
|
const LEAKED_PREFIX_PATTERN =
|
|
1318
|
-
/^\s*(?:[-*•]\s*)?(?:(?:through\s+activity|activity|checkpoint)\s+\d+\s*[:.\-—–]\s*|(?:tldr|summary)\s*[:.\-—–]\s*)+/i;
|
|
1338
|
+
/^\s*(?:[-*•]\s*)?(?:(?:through\s+activity|activity|checkpoint)\s+\d+\s*[:.\-—–]\s*|(?:tldr|summary|progress\s+update|progress)\s*[:.\-—–]\s*)+/i;
|
|
1319
1339
|
const LEADING_PUNCT_PATTERN = /^[\s\-—–•*:#.,;]+/;
|
|
1320
1340
|
const TRAILING_PUNCT_PATTERN = /[\s\-—–•*:#.,;]+$/;
|
|
1321
1341
|
|
|
@@ -1369,6 +1389,15 @@ const DANGLING_PREP_CHAIN_PATTERN =
|
|
|
1369
1389
|
// "Reviewing for image update". Collapse the bare verb+prep into just the verb.
|
|
1370
1390
|
const VERB_BARE_PREP_PATTERN =
|
|
1371
1391
|
/\b(Reviewing|Investigating|Updating|Refining|Exploring|Fixing|Implementing|Bumping|Releasing|Preparing|Drafting|Resuming|Pulling|Surveying|Recording)\s+(?:for|in|on|with|after|before|to|at|as|of|by|from)\s+/gi;
|
|
1392
|
+
// Identifier strip removes version numbers like "0.3.4" but can leave stranded
|
|
1393
|
+
// release adjectives behind: "Released latest of package" was "Released latest
|
|
1394
|
+
// 0.3.4 of package"; "Released new with X" was "Released new 0.3.4 with X".
|
|
1395
|
+
// The chain pattern keeps the noun phrase that followed the version. The
|
|
1396
|
+
// trailing pattern handles bare "... new" or "... latest" tails.
|
|
1397
|
+
const RELEASE_FRAGMENT_CHAIN_PATTERN =
|
|
1398
|
+
/\b(Released|Releasing|Bumped|Bumping|Published|Publishing|Updated|Updating|Shipped|Shipping)\s+(?:new|latest|version|update)\s+(?:of|with|to|and|in|on|for)\s+/gi;
|
|
1399
|
+
const RELEASE_FRAGMENT_TRAILING_PATTERN =
|
|
1400
|
+
/\s+(?:new|latest|version|update)\s*(?:of|with|to|and|in|on)?\s*[.!?,;:]?\s*$/i;
|
|
1372
1401
|
|
|
1373
1402
|
function stripDanglingPrepositions(text: string): string {
|
|
1374
1403
|
let cleaned = text;
|
|
@@ -1376,14 +1405,16 @@ function stripDanglingPrepositions(text: string): string {
|
|
|
1376
1405
|
do {
|
|
1377
1406
|
previous = cleaned;
|
|
1378
1407
|
cleaned = cleaned.replace(VERB_BARE_PREP_PATTERN, "$1 ");
|
|
1408
|
+
cleaned = cleaned.replace(RELEASE_FRAGMENT_CHAIN_PATTERN, "$1 ");
|
|
1379
1409
|
cleaned = cleaned.replace(DANGLING_PREP_CHAIN_PATTERN, "$2");
|
|
1410
|
+
cleaned = cleaned.replace(RELEASE_FRAGMENT_TRAILING_PATTERN, "").trim();
|
|
1380
1411
|
cleaned = cleaned.replace(DANGLING_TRAILING_PREP_PATTERN, "").trim();
|
|
1381
1412
|
} while (cleaned !== previous && cleaned.length > 0);
|
|
1382
1413
|
return cleaned;
|
|
1383
1414
|
}
|
|
1384
1415
|
|
|
1385
1416
|
// Last-resort fallback when the model ignored the allow-list and started the
|
|
1386
|
-
//
|
|
1417
|
+
// progress update with a banned tool-narration verb. The map preserves tense (present
|
|
1387
1418
|
// vs past). Case of the first letter follows the original word.
|
|
1388
1419
|
const BANNED_FIRST_WORD_REWRITES: Record<string, string> = {
|
|
1389
1420
|
Reading: "Reviewing",
|
|
@@ -1437,7 +1468,7 @@ function rewriteBannedFirstWord(text: string): string {
|
|
|
1437
1468
|
const original = match[1];
|
|
1438
1469
|
const replacement = BANNED_FIRST_WORD_REWRITES[original];
|
|
1439
1470
|
if (!replacement) return text;
|
|
1440
|
-
// Preserve lowercase-start
|
|
1471
|
+
// Preserve lowercase-start outputs (rare but possible) by matching original case.
|
|
1441
1472
|
const cased =
|
|
1442
1473
|
original[0] === original[0].toLowerCase()
|
|
1443
1474
|
? replacement[0].toLowerCase() + replacement.slice(1)
|
|
@@ -1466,9 +1497,9 @@ function stripSuccessSuffix(text: string): string {
|
|
|
1466
1497
|
return cleaned;
|
|
1467
1498
|
}
|
|
1468
1499
|
|
|
1469
|
-
function
|
|
1500
|
+
function sanitizeProgressText(
|
|
1470
1501
|
text: string,
|
|
1471
|
-
maxChars =
|
|
1502
|
+
maxChars = MAX_SAFE_PROGRESS_CHARS,
|
|
1472
1503
|
): string {
|
|
1473
1504
|
const stripped = stripTerminalControls(text);
|
|
1474
1505
|
const withoutMarkdown = stripMarkdownFormatting(stripped);
|
|
@@ -1576,7 +1607,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1576
1607
|
let statusFilter: StatusFilter = { mode: "all", hidden: new Set() };
|
|
1577
1608
|
const seenStatusKeys = new Set<string>();
|
|
1578
1609
|
const refresh = () => requestRender?.();
|
|
1579
|
-
const
|
|
1610
|
+
const progress = new FooterProgressEngine(refresh);
|
|
1580
1611
|
const restoreStatusFilter = (ctx: ExtensionContext) => {
|
|
1581
1612
|
let restoredFilter = readGlobalStatusFilter();
|
|
1582
1613
|
if (!restoredFilter) {
|
|
@@ -1764,32 +1795,32 @@ export default function (pi: ExtensionAPI) {
|
|
|
1764
1795
|
pi.on("thinking_level_select", async () => refresh());
|
|
1765
1796
|
pi.on("turn_end", async () => refresh());
|
|
1766
1797
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
1767
|
-
|
|
1798
|
+
progress.recordUserMessage(ctx, event.prompt);
|
|
1768
1799
|
});
|
|
1769
1800
|
pi.on("message_update", async (event, ctx) => {
|
|
1770
|
-
|
|
1801
|
+
progress.recordAssistantUpdate(ctx, event.message);
|
|
1771
1802
|
});
|
|
1772
1803
|
pi.on("tool_call", async (event, ctx) => {
|
|
1773
|
-
|
|
1804
|
+
progress.recordToolCall(ctx, event);
|
|
1774
1805
|
});
|
|
1775
1806
|
pi.on("tool_result", async (event, ctx) => {
|
|
1776
|
-
|
|
1807
|
+
progress.recordToolResult(ctx, event);
|
|
1777
1808
|
});
|
|
1778
1809
|
pi.on("message_end", async (event, ctx) => {
|
|
1779
|
-
|
|
1810
|
+
progress.recordMessageEnd(ctx, event.message);
|
|
1780
1811
|
refresh();
|
|
1781
1812
|
});
|
|
1782
1813
|
pi.on("session_before_tree", async () => {
|
|
1783
|
-
|
|
1814
|
+
progress.shutdown();
|
|
1784
1815
|
});
|
|
1785
1816
|
pi.on("session_tree", async (_event, ctx) => {
|
|
1786
|
-
|
|
1817
|
+
progress.startSession(ctx.cwd);
|
|
1787
1818
|
restoreStatusFilter(ctx);
|
|
1788
1819
|
refresh();
|
|
1789
1820
|
});
|
|
1790
1821
|
|
|
1791
1822
|
pi.on("session_start", async (_event, ctx) => {
|
|
1792
|
-
|
|
1823
|
+
progress.startSession(ctx.cwd);
|
|
1793
1824
|
restoreStatusFilter(ctx);
|
|
1794
1825
|
|
|
1795
1826
|
if (!ctx.hasUI) return;
|
|
@@ -1823,13 +1854,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
1823
1854
|
const contextText = usage
|
|
1824
1855
|
? `${usage.percent !== null ? `${usage.percent.toFixed(1)}%` : "—%"} / ${formatTokens(usage.contextWindow)}`
|
|
1825
1856
|
: "—";
|
|
1826
|
-
const
|
|
1857
|
+
const progressText = progress.text();
|
|
1827
1858
|
|
|
1828
1859
|
const segmentRenderers: Record<SegmentName, string | null> = {
|
|
1829
1860
|
model: theme.fg("accent", modelName),
|
|
1830
1861
|
thinking: theme.fg(thinkingColor(thinkingLevel), `think:${thinkingLevel}`),
|
|
1831
1862
|
context: theme.fg(contextSegmentColor, contextText),
|
|
1832
|
-
|
|
1863
|
+
progress: progressText ? theme.fg("muted", progressText) : null,
|
|
1833
1864
|
extensions: extensionStatuses ? theme.fg("muted", extensionStatuses) : null,
|
|
1834
1865
|
};
|
|
1835
1866
|
|
|
@@ -1846,7 +1877,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1846
1877
|
});
|
|
1847
1878
|
|
|
1848
1879
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
1849
|
-
|
|
1880
|
+
progress.shutdown();
|
|
1850
1881
|
if (ctx.hasUI) ctx.ui.setFooter(undefined);
|
|
1851
1882
|
});
|
|
1852
1883
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-bar",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Pi footer/statusline showing active model, thinking level, context pressure, live
|
|
3
|
+
"version": "0.3.30",
|
|
4
|
+
"description": "Pi footer/statusline showing active model, thinking level, context pressure, live progress updates, and extension statuses.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"thinking",
|
|
15
15
|
"context",
|
|
16
16
|
"context-window",
|
|
17
|
+
"progress",
|
|
17
18
|
"tldr"
|
|
18
19
|
],
|
|
19
20
|
"license": "MIT",
|
|
@@ -51,6 +52,6 @@
|
|
|
51
52
|
"extensions": [
|
|
52
53
|
"./extensions/status-footer.ts"
|
|
53
54
|
],
|
|
54
|
-
"image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.
|
|
55
|
+
"image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.30/assets/screenshot-yellow.png"
|
|
55
56
|
}
|
|
56
57
|
}
|