pi-bar 0.3.25 → 0.3.27
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 +3 -3
- package/extensions/status-footer.ts +164 -359
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
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
|
|
|
@@ -43,11 +43,7 @@ type SerializedStatusFilter =
|
|
|
43
43
|
type TldrActivityType =
|
|
44
44
|
| "user_message"
|
|
45
45
|
| "assistant_update"
|
|
46
|
-
| "tool_input_start"
|
|
47
|
-
| "tool_input_update"
|
|
48
|
-
| "tool_input_end"
|
|
49
46
|
| "tool_call"
|
|
50
|
-
| "tool_execution_update"
|
|
51
47
|
| "tool_result"
|
|
52
48
|
| "assistant_final"
|
|
53
49
|
| "assistant_failure";
|
|
@@ -58,36 +54,17 @@ type TldrActivity = {
|
|
|
58
54
|
displayPriority: TldrDisplayPriority;
|
|
59
55
|
text: string;
|
|
60
56
|
toolCallId?: string;
|
|
61
|
-
// Identifies a continuous streaming run (tool input arg streaming, or tool
|
|
62
|
-
// execution output streaming). Activities sharing a group represent updates
|
|
63
|
-
// to the same task; staleness handling treats them as one stream.
|
|
64
|
-
progressGroup?: string;
|
|
65
57
|
};
|
|
66
58
|
type TldrCheckpoint = {
|
|
67
59
|
activityIndex: number;
|
|
68
|
-
activityType: TldrActivityType;
|
|
69
60
|
displayPriority: TldrDisplayPriority;
|
|
70
61
|
text: string;
|
|
71
|
-
progressGroup?: string;
|
|
72
62
|
};
|
|
73
63
|
type TldrCheckpointJob = {
|
|
74
64
|
activityIndex: number;
|
|
75
|
-
activityType: TldrActivityType;
|
|
76
65
|
displayPriority: TldrDisplayPriority;
|
|
77
66
|
runId: number;
|
|
78
|
-
progressGroup?: string;
|
|
79
67
|
};
|
|
80
|
-
|
|
81
|
-
// Activities that represent ongoing streaming work. These are treated
|
|
82
|
-
// specially: their checkpoints can render even when newer same-stream activity
|
|
83
|
-
// exists, and the display refresh slows to a 10s cadence for repeat renders so
|
|
84
|
-
// the footer does not become a model-call ticker.
|
|
85
|
-
function isToolProgressActivity(activityType: TldrActivityType): boolean {
|
|
86
|
-
return (
|
|
87
|
-
activityType === "tool_input_update" ||
|
|
88
|
-
activityType === "tool_execution_update"
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
68
|
type TldrModelPreference = { provider: string; id: string };
|
|
92
69
|
type FastModelAuth = {
|
|
93
70
|
model: Parameters<typeof complete>[0];
|
|
@@ -111,16 +88,12 @@ const TLDR_MAX_TOKENS = 120;
|
|
|
111
88
|
const TLDR_REQUEST_TIMEOUT_MS = 2_000;
|
|
112
89
|
const TLDR_DISPLAY_UPDATE_INTERVAL_MS = 1_200;
|
|
113
90
|
const TLDR_TARGET_SUMMARY_CHARS = 60;
|
|
114
|
-
// While rendering successive progress checkpoints from the same stream (e.g.
|
|
115
|
-
// long bash output), slow the display refresh dramatically. The TLDR text
|
|
116
|
-
// barely changes between adjacent chunks; refreshing every 1.2s would burn API
|
|
117
|
-
// calls and footer renders for noise.
|
|
118
|
-
const TOOL_PROGRESS_DISPLAY_UPDATE_INTERVAL_MS = 10_000;
|
|
119
|
-
const MAX_TOOL_PROGRESS_PARTIAL_CHARS = 280;
|
|
120
91
|
// Burst debouncing on the generation side: coalesce rapid-fire activities into
|
|
121
92
|
// one model call. Quiet window catches short bursts; max wait surfaces progress
|
|
122
93
|
// during continuous activity.
|
|
123
|
-
|
|
94
|
+
// Quiet window long enough to let tool_result land before flushing, so the
|
|
95
|
+
// model summarizes the completed outcome instead of guessing from tool_call.
|
|
96
|
+
const NORMAL_CHECKPOINT_QUIET_MS = 1_500;
|
|
124
97
|
const NORMAL_CHECKPOINT_MAX_WAIT_MS = 2_500;
|
|
125
98
|
// Defensive ceiling: even if the model ignores the length instruction, never
|
|
126
99
|
// blast a large payload into the footer.
|
|
@@ -477,58 +450,13 @@ function extractTextContent(content: readonly unknown[] | undefined): string | u
|
|
|
477
450
|
return compacted.length > 0 ? compacted : undefined;
|
|
478
451
|
}
|
|
479
452
|
|
|
480
|
-
type ToolInputAssistantEvent =
|
|
481
|
-
| { type: "toolcall_start"; contentIndex: number; partial: unknown }
|
|
482
|
-
| {
|
|
483
|
-
type: "toolcall_delta";
|
|
484
|
-
contentIndex: number;
|
|
485
|
-
delta: string;
|
|
486
|
-
partial: unknown;
|
|
487
|
-
}
|
|
488
|
-
| {
|
|
489
|
-
type: "toolcall_end";
|
|
490
|
-
contentIndex: number;
|
|
491
|
-
toolCall: { id: string; name: string; arguments?: unknown };
|
|
492
|
-
partial: unknown;
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
function toolCallAtContentIndex(
|
|
496
|
-
partial: unknown,
|
|
497
|
-
contentIndex: number,
|
|
498
|
-
): { id: string; name: string; arguments?: unknown } | undefined {
|
|
499
|
-
if (!partial || typeof partial !== "object") return undefined;
|
|
500
|
-
const content = (partial as { content?: unknown }).content;
|
|
501
|
-
if (!Array.isArray(content)) return undefined;
|
|
502
|
-
const block = content[contentIndex];
|
|
503
|
-
if (!block || typeof block !== "object") return undefined;
|
|
504
|
-
const record = block as Record<string, unknown>;
|
|
505
|
-
if (record.type !== "toolCall") return undefined;
|
|
506
|
-
if (typeof record.id !== "string" || typeof record.name !== "string") {
|
|
507
|
-
return undefined;
|
|
508
|
-
}
|
|
509
|
-
return {
|
|
510
|
-
id: record.id,
|
|
511
|
-
name: record.name,
|
|
512
|
-
arguments: record.arguments,
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
|
|
516
453
|
class TldrFactCollector {
|
|
517
454
|
private nextIndex = 1;
|
|
518
455
|
private readonly activities: TldrActivity[] = [];
|
|
519
|
-
// Streaming tool inputs (`toolcall_start/delta/end` from message_update)
|
|
520
|
-
// arrive before the final tool id is available; content index is the best
|
|
521
|
-
// stable anchor until the id appears.
|
|
522
|
-
private readonly activeToolInputAnchors = new Map<string, string>();
|
|
523
456
|
|
|
524
457
|
resetConversation(): void {
|
|
525
458
|
this.nextIndex = 1;
|
|
526
459
|
this.activities.splice(0);
|
|
527
|
-
this.activeToolInputAnchors.clear();
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
latestActivity(): TldrActivity | undefined {
|
|
531
|
-
return this.activities[this.activities.length - 1];
|
|
532
460
|
}
|
|
533
461
|
|
|
534
462
|
recordUserMessage(prompt: string): TldrActivity {
|
|
@@ -560,79 +488,6 @@ class TldrFactCollector {
|
|
|
560
488
|
);
|
|
561
489
|
}
|
|
562
490
|
|
|
563
|
-
// Stage 1: tool input streaming. The model is writing tool name + args; pi
|
|
564
|
-
// surfaces this via `assistantMessageEvent` on `message_update` with
|
|
565
|
-
// toolcall_start / toolcall_delta / toolcall_end. Useful when args are long
|
|
566
|
-
// (big edits, structured plans) and the user wants visible progress before
|
|
567
|
-
// the actual tool execution starts.
|
|
568
|
-
recordToolInputActivity(
|
|
569
|
-
event: ToolInputAssistantEvent | undefined,
|
|
570
|
-
): TldrActivity | undefined {
|
|
571
|
-
if (!event) return undefined;
|
|
572
|
-
|
|
573
|
-
switch (event.type) {
|
|
574
|
-
case "toolcall_start": {
|
|
575
|
-
const toolCall = toolCallAtContentIndex(event.partial, event.contentIndex);
|
|
576
|
-
const toolName = toolCall?.name ?? "unknown";
|
|
577
|
-
const anchorKey = toolCall?.id ?? `content:${event.contentIndex}`;
|
|
578
|
-
const anchorText = `tool input: ${toolName}`;
|
|
579
|
-
this.activeToolInputAnchors.set(anchorKey, anchorText);
|
|
580
|
-
return this.addActivity(
|
|
581
|
-
"tool_input_start",
|
|
582
|
-
"normal",
|
|
583
|
-
anchorText,
|
|
584
|
-
toolCall?.id,
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
|
-
case "toolcall_delta": {
|
|
588
|
-
const toolCall = toolCallAtContentIndex(event.partial, event.contentIndex);
|
|
589
|
-
const anchorKey = toolCall?.id ?? `content:${event.contentIndex}`;
|
|
590
|
-
const anchorText =
|
|
591
|
-
this.activeToolInputAnchors.get(anchorKey) ??
|
|
592
|
-
`tool input: ${toolCall?.name ?? "unknown"}`;
|
|
593
|
-
|
|
594
|
-
// Repeated delta updates for the same call collapse into the latest
|
|
595
|
-
// snapshot. Otherwise long arg streams would push out useful facts.
|
|
596
|
-
const last = this.activities[this.activities.length - 1];
|
|
597
|
-
if (
|
|
598
|
-
last?.activityType === "tool_input_update" &&
|
|
599
|
-
last.progressGroup === `tool-input:${anchorKey}`
|
|
600
|
-
) {
|
|
601
|
-
this.activities.pop();
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
const argSnapshot = compactValue(toolCall?.arguments, 200);
|
|
605
|
-
const chunk = event.delta
|
|
606
|
-
? `latest: ${truncateText(compactText(event.delta), 120)}`
|
|
607
|
-
: undefined;
|
|
608
|
-
const update = [argSnapshot, chunk].filter(Boolean).join("; ");
|
|
609
|
-
|
|
610
|
-
return this.addActivity(
|
|
611
|
-
"tool_input_update",
|
|
612
|
-
"normal",
|
|
613
|
-
`${anchorText}; ${update || "streaming"}`,
|
|
614
|
-
toolCall?.id,
|
|
615
|
-
`tool-input:${anchorKey}`,
|
|
616
|
-
);
|
|
617
|
-
}
|
|
618
|
-
case "toolcall_end": {
|
|
619
|
-
const anchorKey = event.toolCall.id;
|
|
620
|
-
const anchorText =
|
|
621
|
-
this.activeToolInputAnchors.get(anchorKey) ??
|
|
622
|
-
`tool input: ${event.toolCall.name}`;
|
|
623
|
-
this.activeToolInputAnchors.delete(anchorKey);
|
|
624
|
-
|
|
625
|
-
const args = compactValue(event.toolCall.arguments, 240);
|
|
626
|
-
return this.addActivity(
|
|
627
|
-
"tool_input_end",
|
|
628
|
-
"normal",
|
|
629
|
-
args ? `${anchorText} completed; ${args}` : `${anchorText} completed`,
|
|
630
|
-
event.toolCall.id,
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
491
|
recordToolCall(event: {
|
|
637
492
|
toolName: string;
|
|
638
493
|
input?: Record<string, unknown>;
|
|
@@ -646,45 +501,6 @@ class TldrFactCollector {
|
|
|
646
501
|
);
|
|
647
502
|
}
|
|
648
503
|
|
|
649
|
-
// Stage 2: tool execution streaming. The host is running the tool and
|
|
650
|
-
// emitting partial output. Tagged with progressGroup so adjacent chunks of
|
|
651
|
-
// the same execution are treated as one continuous stream.
|
|
652
|
-
recordToolExecutionUpdate(event: {
|
|
653
|
-
toolCallId: string;
|
|
654
|
-
toolName: string;
|
|
655
|
-
partialResult: unknown;
|
|
656
|
-
}): TldrActivity {
|
|
657
|
-
const partialText =
|
|
658
|
-
extractTextContent(
|
|
659
|
-
Array.isArray((event.partialResult as { content?: unknown })?.content)
|
|
660
|
-
? ((event.partialResult as { content: unknown[] }).content as unknown[])
|
|
661
|
-
: undefined,
|
|
662
|
-
) ??
|
|
663
|
-
compactValue(event.partialResult, MAX_TOOL_PROGRESS_PARTIAL_CHARS) ??
|
|
664
|
-
"streaming";
|
|
665
|
-
|
|
666
|
-
const progressGroup = `tool-execution:${event.toolCallId}`;
|
|
667
|
-
|
|
668
|
-
// Adjacent execution-update activities for the same stream collapse into
|
|
669
|
-
// the latest. The retained activity list stays bounded and the model sees
|
|
670
|
-
// one current snapshot of in-progress output.
|
|
671
|
-
const last = this.activities[this.activities.length - 1];
|
|
672
|
-
if (
|
|
673
|
-
last?.activityType === "tool_execution_update" &&
|
|
674
|
-
last.progressGroup === progressGroup
|
|
675
|
-
) {
|
|
676
|
-
this.activities.pop();
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
return this.addActivity(
|
|
680
|
-
"tool_execution_update",
|
|
681
|
-
"normal",
|
|
682
|
-
`running ${event.toolName}; ${tailText(partialText, MAX_TOOL_PROGRESS_PARTIAL_CHARS)}`,
|
|
683
|
-
event.toolCallId,
|
|
684
|
-
progressGroup,
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
|
|
688
504
|
recordToolResult(event: {
|
|
689
505
|
toolName: string;
|
|
690
506
|
input?: Record<string, unknown>;
|
|
@@ -703,24 +519,15 @@ class TldrFactCollector {
|
|
|
703
519
|
const status = isError ? "error" : "ok";
|
|
704
520
|
const tool = compactToolInput(event.toolName, event.input);
|
|
705
521
|
|
|
706
|
-
//
|
|
707
|
-
//
|
|
708
|
-
// tool_execution_update progress. The model only needs the final outcome.
|
|
522
|
+
// Result fact subsumes the prior tool_call fact for the same call. Drop the
|
|
523
|
+
// matching tool_call so the model sees one combined fact instead of two.
|
|
709
524
|
if (event.toolCallId) {
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
activity.activityType === "tool_input_start" ||
|
|
717
|
-
activity.activityType === "tool_input_update" ||
|
|
718
|
-
activity.activityType === "tool_input_end" ||
|
|
719
|
-
activity.activityType === "tool_execution_update"
|
|
720
|
-
) {
|
|
721
|
-
this.activities.splice(i, 1);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
525
|
+
const idx = this.activities.findIndex(
|
|
526
|
+
(activity) =>
|
|
527
|
+
activity.activityType === "tool_call" &&
|
|
528
|
+
activity.toolCallId === event.toolCallId,
|
|
529
|
+
);
|
|
530
|
+
if (idx !== -1) this.activities.splice(idx, 1);
|
|
724
531
|
}
|
|
725
532
|
|
|
726
533
|
return this.addActivity(
|
|
@@ -788,15 +595,13 @@ class TldrFactCollector {
|
|
|
788
595
|
displayPriority: TldrDisplayPriority,
|
|
789
596
|
text: string,
|
|
790
597
|
toolCallId?: string,
|
|
791
|
-
progressGroup?: string,
|
|
792
598
|
): TldrActivity {
|
|
793
|
-
const activity
|
|
599
|
+
const activity = {
|
|
794
600
|
index: this.nextIndex,
|
|
795
601
|
activityType,
|
|
796
602
|
displayPriority,
|
|
797
603
|
text: truncateText(text, MAX_ACTIVITY_TEXT_CHARS),
|
|
798
604
|
toolCallId,
|
|
799
|
-
progressGroup,
|
|
800
605
|
};
|
|
801
606
|
this.nextIndex++;
|
|
802
607
|
this.activities.push(activity);
|
|
@@ -828,9 +633,6 @@ class FooterTldrEngine {
|
|
|
828
633
|
private pendingNormalCheckpoint?: TldrCheckpointJob;
|
|
829
634
|
private normalCheckpointTimer?: ReturnType<typeof setTimeout>;
|
|
830
635
|
private normalCheckpointBurstStartedAt?: number;
|
|
831
|
-
// Last progress stream that produced a rendered checkpoint. Used to slow the
|
|
832
|
-
// display refresh for repeat renders from the same stream.
|
|
833
|
-
private lastRenderedProgressGroup?: string;
|
|
834
636
|
|
|
835
637
|
constructor(private readonly requestRender: () => void) {}
|
|
836
638
|
|
|
@@ -850,47 +652,32 @@ class FooterTldrEngine {
|
|
|
850
652
|
}
|
|
851
653
|
|
|
852
654
|
recordUserMessage(ctx: ExtensionContext, prompt: string): void {
|
|
655
|
+
// A new user turn replaces the entire visible TLDR context, so wipe both
|
|
656
|
+
// the rendered text and the prior-turn checkpoint context that feeds the
|
|
657
|
+
// model. Carrying old checkpoints biased the next turn's phrasing toward
|
|
658
|
+
// the previous task.
|
|
853
659
|
this.currentText = null;
|
|
660
|
+
this.lastRenderedText = "";
|
|
661
|
+
this.acceptedCheckpoints.splice(0);
|
|
662
|
+
this.latestAcceptedActivityIndex = 0;
|
|
854
663
|
this.requestRender();
|
|
855
664
|
this.enqueue(ctx, this.facts.recordUserMessage(prompt));
|
|
856
665
|
}
|
|
857
666
|
|
|
858
|
-
recordAssistantUpdate(
|
|
859
|
-
ctx: ExtensionContext,
|
|
860
|
-
message: unknown,
|
|
861
|
-
assistantMessageEvent?: unknown,
|
|
862
|
-
): void {
|
|
863
|
-
// Tool input streaming hides inside `message_update`. Drain that signal
|
|
864
|
-
// first; only if no tool-input fact was produced does this look like a
|
|
865
|
-
// plain assistant text update worth recording.
|
|
866
|
-
const toolInputActivity = this.facts.recordToolInputActivity(
|
|
867
|
-
normalizeToolInputEvent(assistantMessageEvent),
|
|
868
|
-
);
|
|
869
|
-
if (toolInputActivity) {
|
|
870
|
-
this.enqueue(ctx, toolInputActivity);
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
|
|
667
|
+
recordAssistantUpdate(ctx: ExtensionContext, message: unknown): void {
|
|
874
668
|
const activity = this.facts.recordAssistantUpdate(message);
|
|
875
669
|
if (activity) this.enqueue(ctx, activity);
|
|
876
670
|
}
|
|
877
671
|
|
|
878
672
|
recordToolCall(
|
|
879
|
-
|
|
673
|
+
_ctx: ExtensionContext,
|
|
880
674
|
event: { toolName: string; input?: Record<string, unknown>; toolCallId?: string },
|
|
881
675
|
): void {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
event: {
|
|
888
|
-
toolCallId: string;
|
|
889
|
-
toolName: string;
|
|
890
|
-
partialResult: unknown;
|
|
891
|
-
},
|
|
892
|
-
): void {
|
|
893
|
-
this.enqueue(ctx, this.facts.recordToolExecutionUpdate(event));
|
|
676
|
+
// Record the fact so a subsequent checkpoint sees the tool was started,
|
|
677
|
+
// but DO NOT enqueue. tool_call alone has no outcome; flushing here
|
|
678
|
+
// before tool_result arrives makes the model hallucinate "completed
|
|
679
|
+
// successfully". The tool_result event triggers the actual checkpoint.
|
|
680
|
+
this.facts.recordToolCall(event);
|
|
894
681
|
}
|
|
895
682
|
|
|
896
683
|
recordToolResult(
|
|
@@ -915,9 +702,33 @@ class FooterTldrEngine {
|
|
|
915
702
|
this.startFreshRun();
|
|
916
703
|
return;
|
|
917
704
|
}
|
|
705
|
+
|
|
706
|
+
// Failure/aborted finals have no narratable content. A model call here
|
|
707
|
+
// fabricates a reason; render a deterministic literal instead.
|
|
708
|
+
if (result.activityType === "assistant_failure") {
|
|
709
|
+
const stopReason = extractStopReason(message);
|
|
710
|
+
this.renderLiteralFinal(stopReason);
|
|
711
|
+
this.facts.resetConversation();
|
|
712
|
+
this.startFreshRun();
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
|
|
918
716
|
this.enqueue(ctx, result);
|
|
919
717
|
}
|
|
920
718
|
|
|
719
|
+
private renderLiteralFinal(stopReason: string | undefined): void {
|
|
720
|
+
this.clearPendingDisplay();
|
|
721
|
+
this.clearPendingNormalCheckpoint();
|
|
722
|
+
this.checkpointQueue.splice(0);
|
|
723
|
+
this.abortInFlightCheckpoint();
|
|
724
|
+
const literal = stopReason === "aborted" ? "Aborted." : `Stopped: ${stopReason ?? "error"}.`;
|
|
725
|
+
this.currentText = literal;
|
|
726
|
+
this.lastRenderedText = literal;
|
|
727
|
+
this.lastRenderedActivityIndex = this.facts.latestActivityIndex();
|
|
728
|
+
this.lastDisplayAt = performance.now();
|
|
729
|
+
this.requestRender();
|
|
730
|
+
}
|
|
731
|
+
|
|
921
732
|
private startFreshRun(): void {
|
|
922
733
|
this.runId++;
|
|
923
734
|
this.cancelWork();
|
|
@@ -927,7 +738,6 @@ class FooterTldrEngine {
|
|
|
927
738
|
this.lastDisplayAt = Number.NEGATIVE_INFINITY;
|
|
928
739
|
this.acceptedCheckpoints.splice(0);
|
|
929
740
|
this.pendingDisplayCheckpoint = undefined;
|
|
930
|
-
this.lastRenderedProgressGroup = undefined;
|
|
931
741
|
this.currentText = null;
|
|
932
742
|
this.requestRender();
|
|
933
743
|
}
|
|
@@ -996,12 +806,10 @@ class FooterTldrEngine {
|
|
|
996
806
|
|
|
997
807
|
private enqueue(ctx: ExtensionContext, activity: TldrActivity): void {
|
|
998
808
|
if (!ctx.hasUI) return;
|
|
999
|
-
const job
|
|
809
|
+
const job = {
|
|
1000
810
|
activityIndex: activity.index,
|
|
1001
|
-
activityType: activity.activityType,
|
|
1002
811
|
displayPriority: activity.displayPriority,
|
|
1003
812
|
runId: this.runId,
|
|
1004
|
-
progressGroup: activity.progressGroup,
|
|
1005
813
|
};
|
|
1006
814
|
|
|
1007
815
|
if (job.displayPriority === "immediate") {
|
|
@@ -1035,13 +843,9 @@ class FooterTldrEngine {
|
|
|
1035
843
|
this.scheduleNormalCheckpoint(ctx, job);
|
|
1036
844
|
}
|
|
1037
845
|
|
|
1038
|
-
private
|
|
846
|
+
private clearPendingDisplay(): void {
|
|
1039
847
|
if (this.displayTimer) clearTimeout(this.displayTimer);
|
|
1040
848
|
this.displayTimer = undefined;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
private clearPendingDisplay(): void {
|
|
1044
|
-
this.clearDisplayTimer();
|
|
1045
849
|
this.pendingDisplayCheckpoint = undefined;
|
|
1046
850
|
}
|
|
1047
851
|
|
|
@@ -1099,12 +903,10 @@ class FooterTldrEngine {
|
|
|
1099
903
|
const rawText = extractTextContent(response.content) ?? "";
|
|
1100
904
|
const text = sanitizeTldrText(rawText);
|
|
1101
905
|
if (!text) return;
|
|
1102
|
-
const checkpoint
|
|
906
|
+
const checkpoint = {
|
|
1103
907
|
activityIndex: job.activityIndex,
|
|
1104
|
-
activityType: job.activityType,
|
|
1105
908
|
displayPriority: job.displayPriority,
|
|
1106
909
|
text,
|
|
1107
|
-
progressGroup: job.progressGroup,
|
|
1108
910
|
};
|
|
1109
911
|
this.acceptCheckpoint(checkpoint);
|
|
1110
912
|
this.considerDisplayingCheckpoint(ctx, checkpoint);
|
|
@@ -1136,7 +938,7 @@ class FooterTldrEngine {
|
|
|
1136
938
|
checkpoint: TldrCheckpoint,
|
|
1137
939
|
): void {
|
|
1138
940
|
if (checkpoint.activityIndex <= this.lastRenderedActivityIndex) return;
|
|
1139
|
-
if (this.
|
|
941
|
+
if (checkpoint.activityIndex !== this.facts.latestActivityIndex()) return;
|
|
1140
942
|
|
|
1141
943
|
if (checkpoint.displayPriority !== "normal" || !this.lastRenderedText) {
|
|
1142
944
|
this.clearPendingDisplay();
|
|
@@ -1144,77 +946,37 @@ class FooterTldrEngine {
|
|
|
1144
946
|
return;
|
|
1145
947
|
}
|
|
1146
948
|
|
|
1147
|
-
const displayIntervalMs = this.displayIntervalFor(checkpoint);
|
|
1148
949
|
const elapsedMs = performance.now() - this.lastDisplayAt;
|
|
1149
|
-
if (elapsedMs >=
|
|
950
|
+
if (elapsedMs >= TLDR_DISPLAY_UPDATE_INTERVAL_MS) {
|
|
1150
951
|
this.clearPendingDisplay();
|
|
1151
952
|
this.renderCheckpoint(checkpoint);
|
|
1152
953
|
return;
|
|
1153
954
|
}
|
|
1154
955
|
|
|
1155
956
|
this.pendingDisplayCheckpoint = checkpoint;
|
|
1156
|
-
|
|
1157
|
-
// 10s progress-stream debounce should not block a subsequent ordinary
|
|
1158
|
-
// update that wants to render at the normal 1.2s cadence.
|
|
1159
|
-
this.clearDisplayTimer();
|
|
957
|
+
if (this.displayTimer) return;
|
|
1160
958
|
this.displayTimer = setTimeout(() => {
|
|
1161
959
|
this.displayTimer = undefined;
|
|
1162
960
|
const pending = this.pendingDisplayCheckpoint;
|
|
1163
961
|
this.pendingDisplayCheckpoint = undefined;
|
|
1164
|
-
if (!pending) return;
|
|
1165
|
-
if (this.shouldDropStaleCheckpoint(pending)) return;
|
|
962
|
+
if (!pending || pending.activityIndex !== this.facts.latestActivityIndex()) return;
|
|
1166
963
|
this.renderCheckpoint(pending);
|
|
1167
|
-
},
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
// A model call for chunk N may finish after chunk N+1 has been recorded.
|
|
1171
|
-
// Most stale checkpoints are dropped, but progress streams are an exception:
|
|
1172
|
-
// while the same stream is still active, a slightly-behind TLDR is still
|
|
1173
|
-
// useful, and dropping it would leave the footer stuck on older text.
|
|
1174
|
-
private shouldDropStaleCheckpoint(checkpoint: TldrCheckpoint): boolean {
|
|
1175
|
-
if (checkpoint.activityIndex === this.facts.latestActivityIndex()) {
|
|
1176
|
-
return false;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// Boundary checkpoints (user_message, assistant_final, etc.) must stay
|
|
1180
|
-
// latest-only. A stale prompt-start or final TLDR would describe the wrong
|
|
1181
|
-
// turn entirely.
|
|
1182
|
-
if (checkpoint.displayPriority !== "normal") return true;
|
|
1183
|
-
|
|
1184
|
-
if (!isToolProgressActivity(checkpoint.activityType)) return true;
|
|
1185
|
-
if (!checkpoint.progressGroup) return true;
|
|
1186
|
-
|
|
1187
|
-
const latestActivity = this.facts.latestActivity();
|
|
1188
|
-
return !(
|
|
1189
|
-
latestActivity &&
|
|
1190
|
-
latestActivity.index > checkpoint.activityIndex &&
|
|
1191
|
-
latestActivity.activityType === checkpoint.activityType &&
|
|
1192
|
-
latestActivity.progressGroup === checkpoint.progressGroup
|
|
1193
|
-
);
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
private displayIntervalFor(checkpoint: TldrCheckpoint): number {
|
|
1197
|
-
if (!isToolProgressActivity(checkpoint.activityType)) {
|
|
1198
|
-
return TLDR_DISPLAY_UPDATE_INTERVAL_MS;
|
|
1199
|
-
}
|
|
1200
|
-
// First render from a new progress group uses the normal cadence so the
|
|
1201
|
-
// user sees the stream's first TLDR quickly. Subsequent renders for the
|
|
1202
|
-
// same group slow down: the model output barely changes between chunks.
|
|
1203
|
-
return checkpoint.progressGroup &&
|
|
1204
|
-
checkpoint.progressGroup === this.lastRenderedProgressGroup
|
|
1205
|
-
? TOOL_PROGRESS_DISPLAY_UPDATE_INTERVAL_MS
|
|
1206
|
-
: TLDR_DISPLAY_UPDATE_INTERVAL_MS;
|
|
964
|
+
}, TLDR_DISPLAY_UPDATE_INTERVAL_MS - elapsedMs);
|
|
1207
965
|
}
|
|
1208
966
|
|
|
1209
967
|
private renderCheckpoint(checkpoint: TldrCheckpoint): void {
|
|
1210
968
|
if (checkpoint.activityIndex <= this.lastRenderedActivityIndex) return;
|
|
1211
969
|
if (checkpoint.text === this.lastRenderedText) return;
|
|
970
|
+
// Skip near-duplicates: a burst of consecutive checkpoints often produces
|
|
971
|
+
// the same action fragment with only punctuation/casing differences. They
|
|
972
|
+
// flash visibly in the footer without adding information.
|
|
973
|
+
if (isNearDuplicateTldr(checkpoint.text, this.lastRenderedText)) {
|
|
974
|
+
this.lastRenderedActivityIndex = checkpoint.activityIndex;
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
1212
977
|
this.lastRenderedActivityIndex = checkpoint.activityIndex;
|
|
1213
978
|
this.lastRenderedText = checkpoint.text;
|
|
1214
979
|
this.lastDisplayAt = performance.now();
|
|
1215
|
-
this.lastRenderedProgressGroup = isToolProgressActivity(checkpoint.activityType)
|
|
1216
|
-
? checkpoint.progressGroup
|
|
1217
|
-
: undefined;
|
|
1218
980
|
this.currentText = checkpoint.text;
|
|
1219
981
|
this.requestRender();
|
|
1220
982
|
}
|
|
@@ -1252,12 +1014,19 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
|
|
|
1252
1014
|
? "Start with a past-tense verb."
|
|
1253
1015
|
: "Start with a present-tense -ing verb.";
|
|
1254
1016
|
// Frame the model as a human developer describing visible work, not an agent
|
|
1255
|
-
// summarizing its own mechanics.
|
|
1256
|
-
//
|
|
1017
|
+
// summarizing its own mechanics. The banned phrases and few-shot examples
|
|
1018
|
+
// fix concrete regressions observed in backtests: tool-name verbs, success
|
|
1019
|
+
// suffixes, file path leaks, and markdown formatting.
|
|
1257
1020
|
return `Write one plain-English TLDR for a Pi coding agent.
|
|
1258
1021
|
Describe the work progress as if a human developer were doing it.
|
|
1259
1022
|
Focus on the task activity and current outcome, not agent mechanics.
|
|
1260
1023
|
Do not mention tools, tool calls, prompts, messages, model output, or implementation details.
|
|
1024
|
+
Do not start with tool-name verbs such as Read, Reading, Grep, Listing, Counting, Extracting, Displaying, Editing, Writing, Running, Publishing.
|
|
1025
|
+
Use human-developer verbs instead: Reviewing, Investigating, Exploring, Updating, Refining, Fixing, Implementing, Wrapping up.
|
|
1026
|
+
Do not use file paths, file extensions, code identifiers, package names, or version strings.
|
|
1027
|
+
Do not use backticks, asterisks, underscores, quotes, or any markdown formatting.
|
|
1028
|
+
Do not append filler suffixes such as "with success", "successfully", or "completed successfully".
|
|
1029
|
+
Do not claim progress or completion that is not present in the activity.
|
|
1261
1030
|
Use the prior TLDRs for context and the new activity for the update.
|
|
1262
1031
|
Summarize the current state of work; do not narrate the history.
|
|
1263
1032
|
If context is sparse, still summarize the available activity.
|
|
@@ -1268,9 +1037,45 @@ Prefer verb + direct object. Include outcome only if important.
|
|
|
1268
1037
|
Do not address the user.
|
|
1269
1038
|
Output only the status fragment itself. No prefixes, labels, bullets, or quotes.
|
|
1270
1039
|
Plain text only; no markdown, JSON, code, file paths, or tool names.
|
|
1040
|
+
|
|
1041
|
+
Good examples:
|
|
1042
|
+
- Reviewing footer summary behavior
|
|
1043
|
+
- Investigating live TLDR regressions
|
|
1044
|
+
- Refining sanitizer for stray prefixes
|
|
1045
|
+
- Wrapping up extension release
|
|
1046
|
+
|
|
1047
|
+
Bad examples:
|
|
1048
|
+
- Editing extensions/status-footer.ts with success.
|
|
1049
|
+
- Reading status-footer file completed successfully.
|
|
1050
|
+
- Publishing \`pi-bar@0.3.3\` to npm.
|
|
1051
|
+
- Grepping for sanitizeTldrText callers.
|
|
1052
|
+
|
|
1271
1053
|
${tenseInstruction}`;
|
|
1272
1054
|
}
|
|
1273
1055
|
|
|
1056
|
+
function extractStopReason(message: unknown): string | undefined {
|
|
1057
|
+
if (!message || typeof message !== "object") return undefined;
|
|
1058
|
+
const record = message as Record<string, unknown>;
|
|
1059
|
+
const stopReason = record.stopReason;
|
|
1060
|
+
return typeof stopReason === "string" ? stopReason : undefined;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// Two TLDRs are near-duplicates if their normalized action fragments (lowercase,
|
|
1064
|
+
// punctuation/whitespace collapsed) match. Catches bursts of cosmetic variants
|
|
1065
|
+
// like "Editing X." vs "Editing X" vs "editing x.".
|
|
1066
|
+
function normalizedActionFragment(text: string): string {
|
|
1067
|
+
return text
|
|
1068
|
+
.toLowerCase()
|
|
1069
|
+
.replace(/[\p{P}\p{S}]+/gu, " ")
|
|
1070
|
+
.replace(/\s+/gu, " ")
|
|
1071
|
+
.trim();
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function isNearDuplicateTldr(current: string, previous: string): boolean {
|
|
1075
|
+
if (!previous) return false;
|
|
1076
|
+
return normalizedActionFragment(current) === normalizedActionFragment(previous);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1274
1079
|
function previousCheckpointLines(checkpoints: readonly TldrCheckpoint[]): string {
|
|
1275
1080
|
if (checkpoints.length === 0) return "none";
|
|
1276
1081
|
return checkpoints
|
|
@@ -1283,50 +1088,6 @@ function formatRawActivity(activity: TldrActivity): string {
|
|
|
1283
1088
|
return `- ${activity.text}`;
|
|
1284
1089
|
}
|
|
1285
1090
|
|
|
1286
|
-
// Narrows the unknown `assistantMessageEvent` from `message_update` to the
|
|
1287
|
-
// three tool-input streaming variants. Other event shapes (text_start, etc.)
|
|
1288
|
-
// produce undefined, signalling "no tool input fact to record".
|
|
1289
|
-
function normalizeToolInputEvent(value: unknown): ToolInputAssistantEvent | undefined {
|
|
1290
|
-
if (!value || typeof value !== "object") return undefined;
|
|
1291
|
-
const record = value as Record<string, unknown>;
|
|
1292
|
-
const type = record.type;
|
|
1293
|
-
if (type !== "toolcall_start" && type !== "toolcall_delta" && type !== "toolcall_end") {
|
|
1294
|
-
return undefined;
|
|
1295
|
-
}
|
|
1296
|
-
if (typeof record.contentIndex !== "number") return undefined;
|
|
1297
|
-
|
|
1298
|
-
if (type === "toolcall_delta") {
|
|
1299
|
-
return {
|
|
1300
|
-
type,
|
|
1301
|
-
contentIndex: record.contentIndex,
|
|
1302
|
-
delta: typeof record.delta === "string" ? record.delta : "",
|
|
1303
|
-
partial: record.partial,
|
|
1304
|
-
};
|
|
1305
|
-
}
|
|
1306
|
-
if (type === "toolcall_end") {
|
|
1307
|
-
const toolCall = record.toolCall;
|
|
1308
|
-
if (
|
|
1309
|
-
!toolCall ||
|
|
1310
|
-
typeof toolCall !== "object" ||
|
|
1311
|
-
typeof (toolCall as Record<string, unknown>).id !== "string" ||
|
|
1312
|
-
typeof (toolCall as Record<string, unknown>).name !== "string"
|
|
1313
|
-
) {
|
|
1314
|
-
return undefined;
|
|
1315
|
-
}
|
|
1316
|
-
return {
|
|
1317
|
-
type,
|
|
1318
|
-
contentIndex: record.contentIndex,
|
|
1319
|
-
toolCall: toolCall as { id: string; name: string; arguments?: unknown },
|
|
1320
|
-
partial: record.partial,
|
|
1321
|
-
};
|
|
1322
|
-
}
|
|
1323
|
-
return {
|
|
1324
|
-
type,
|
|
1325
|
-
contentIndex: record.contentIndex,
|
|
1326
|
-
partial: record.partial,
|
|
1327
|
-
};
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
1091
|
// Strips ANSI/CSI/OSC/DCS/SOS/PM/APC escape sequences from model output before
|
|
1331
1092
|
// it lands in the terminal. The TLDR text is model-controlled and renders into
|
|
1332
1093
|
// the footer; the system prompt asking for plain text is not a security
|
|
@@ -1436,6 +1197,7 @@ function stripTerminalControls(text: string): string {
|
|
|
1436
1197
|
const LEAKED_PREFIX_PATTERN =
|
|
1437
1198
|
/^\s*(?:[-*•]\s*)?(?:(?:through\s+activity|activity|checkpoint)\s+\d+\s*[:.\-—–]\s*|(?:tldr|summary)\s*[:.\-—–]\s*)+/i;
|
|
1438
1199
|
const LEADING_PUNCT_PATTERN = /^[\s\-—–•*:#.,;]+/;
|
|
1200
|
+
const TRAILING_PUNCT_PATTERN = /[\s\-—–•*:#.,;]+$/;
|
|
1439
1201
|
|
|
1440
1202
|
function stripLeakedScaffolding(text: string): string {
|
|
1441
1203
|
let cleaned = text;
|
|
@@ -1448,13 +1210,63 @@ function stripLeakedScaffolding(text: string): string {
|
|
|
1448
1210
|
return cleaned;
|
|
1449
1211
|
}
|
|
1450
1212
|
|
|
1213
|
+
// Strip backticks (` and triple-backtick fences) and markdown emphasis markers
|
|
1214
|
+
// the model occasionally adds despite the prompt's plain-text rule. We keep
|
|
1215
|
+
// inner content verbatim; only the formatting characters disappear.
|
|
1216
|
+
function stripMarkdownFormatting(text: string): string {
|
|
1217
|
+
return text
|
|
1218
|
+
.replace(/```+/g, "")
|
|
1219
|
+
.replace(/`+/g, "")
|
|
1220
|
+
.replace(/(^|[^\\])([*_~]{1,3})(.+?)\2/g, "$1$3");
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// File-path / version / package leaks observed in backtests:
|
|
1224
|
+
// extensions/status-footer.ts, README.md, package.json, pi-bar@0.3.3, …
|
|
1225
|
+
const FILE_PATH_PATTERN =
|
|
1226
|
+
/(?:\b[\w./@-]+\.(?:ts|tsx|js|jsx|mjs|cjs|md|json|yml|yaml|toml|lock|sh|py|rs|go|html|css))\b/g;
|
|
1227
|
+
const PACKAGE_VERSION_PATTERN = /\b[\w./@-]+@\d[\w.+-]*\b/g;
|
|
1228
|
+
const VERSION_PATTERN = /\bv?\d+\.\d+(?:\.\d+(?:[-+][\w.]+)?)?\b/g;
|
|
1229
|
+
|
|
1230
|
+
function stripIdentifierLeaks(text: string): string {
|
|
1231
|
+
return text
|
|
1232
|
+
.replace(PACKAGE_VERSION_PATTERN, "")
|
|
1233
|
+
.replace(FILE_PATH_PATTERN, "")
|
|
1234
|
+
.replace(VERSION_PATTERN, "")
|
|
1235
|
+
.replace(/\s+/g, " ")
|
|
1236
|
+
.trim();
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// Trailing filler suffixes observed in backtests:
|
|
1240
|
+
// "... with success.", "... completed successfully.", "... successfully."
|
|
1241
|
+
const SUCCESS_SUFFIX_PATTERN =
|
|
1242
|
+
/[\s,;:—–-]*(?:with\s+success|completed\s+successfully|finished\s+successfully|done\s+successfully|successfully\s+completed|successfully\s+finished|successfully)\s*[.!?]*\s*$/i;
|
|
1243
|
+
|
|
1244
|
+
function stripSuccessSuffix(text: string): string {
|
|
1245
|
+
let cleaned = text;
|
|
1246
|
+
let stripped = false;
|
|
1247
|
+
while (true) {
|
|
1248
|
+
const next = cleaned.replace(SUCCESS_SUFFIX_PATTERN, "").trim();
|
|
1249
|
+
if (next === cleaned || next.length === 0) break;
|
|
1250
|
+
cleaned = next;
|
|
1251
|
+
stripped = true;
|
|
1252
|
+
}
|
|
1253
|
+
// Only clean up trailing punctuation when an actual success suffix was
|
|
1254
|
+
// removed; otherwise we would strip the natural period from a normal
|
|
1255
|
+
// sentence like "Reviewing footer behavior.".
|
|
1256
|
+
if (stripped) cleaned = cleaned.replace(TRAILING_PUNCT_PATTERN, "").trim();
|
|
1257
|
+
return cleaned;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1451
1260
|
function sanitizeTldrText(
|
|
1452
1261
|
text: string,
|
|
1453
1262
|
maxChars = MAX_SAFE_TLDR_CHARS,
|
|
1454
1263
|
): string {
|
|
1455
1264
|
const stripped = stripTerminalControls(text);
|
|
1456
|
-
const
|
|
1457
|
-
|
|
1265
|
+
const withoutMarkdown = stripMarkdownFormatting(stripped);
|
|
1266
|
+
const withoutScaffolding = stripLeakedScaffolding(withoutMarkdown) || withoutMarkdown;
|
|
1267
|
+
const withoutLeaks = stripIdentifierLeaks(withoutScaffolding) || withoutScaffolding;
|
|
1268
|
+
const withoutSuccess = stripSuccessSuffix(withoutLeaks) || withoutLeaks;
|
|
1269
|
+
return truncateText(withoutSuccess, maxChars);
|
|
1458
1270
|
}
|
|
1459
1271
|
|
|
1460
1272
|
function shouldShowStatus(key: string, filter: StatusFilter): boolean {
|
|
@@ -1744,18 +1556,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1744
1556
|
tldr.recordUserMessage(ctx, event.prompt);
|
|
1745
1557
|
});
|
|
1746
1558
|
pi.on("message_update", async (event, ctx) => {
|
|
1747
|
-
tldr.recordAssistantUpdate(
|
|
1748
|
-
ctx,
|
|
1749
|
-
event.message,
|
|
1750
|
-
(event as { assistantMessageEvent?: unknown }).assistantMessageEvent,
|
|
1751
|
-
);
|
|
1559
|
+
tldr.recordAssistantUpdate(ctx, event.message);
|
|
1752
1560
|
});
|
|
1753
1561
|
pi.on("tool_call", async (event, ctx) => {
|
|
1754
1562
|
tldr.recordToolCall(ctx, event);
|
|
1755
1563
|
});
|
|
1756
|
-
pi.on("tool_execution_update", async (event, ctx) => {
|
|
1757
|
-
tldr.recordToolExecutionUpdate(ctx, event);
|
|
1758
|
-
});
|
|
1759
1564
|
pi.on("tool_result", async (event, ctx) => {
|
|
1760
1565
|
tldr.recordToolResult(ctx, event);
|
|
1761
1566
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-bar",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.27",
|
|
4
4
|
"description": "Pi footer/statusline showing active model, thinking level, context pressure, live TLDR, and extension statuses.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -51,6 +51,6 @@
|
|
|
51
51
|
"extensions": [
|
|
52
52
|
"./extensions/status-footer.ts"
|
|
53
53
|
],
|
|
54
|
-
"image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.
|
|
54
|
+
"image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.27/assets/screenshot-yellow.png"
|
|
55
55
|
}
|
|
56
56
|
}
|