pi-bar 0.3.24 → 0.3.25
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 +352 -19
- 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,7 +43,11 @@ 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"
|
|
46
49
|
| "tool_call"
|
|
50
|
+
| "tool_execution_update"
|
|
47
51
|
| "tool_result"
|
|
48
52
|
| "assistant_final"
|
|
49
53
|
| "assistant_failure";
|
|
@@ -54,17 +58,36 @@ type TldrActivity = {
|
|
|
54
58
|
displayPriority: TldrDisplayPriority;
|
|
55
59
|
text: string;
|
|
56
60
|
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;
|
|
57
65
|
};
|
|
58
66
|
type TldrCheckpoint = {
|
|
59
67
|
activityIndex: number;
|
|
68
|
+
activityType: TldrActivityType;
|
|
60
69
|
displayPriority: TldrDisplayPriority;
|
|
61
70
|
text: string;
|
|
71
|
+
progressGroup?: string;
|
|
62
72
|
};
|
|
63
73
|
type TldrCheckpointJob = {
|
|
64
74
|
activityIndex: number;
|
|
75
|
+
activityType: TldrActivityType;
|
|
65
76
|
displayPriority: TldrDisplayPriority;
|
|
66
77
|
runId: number;
|
|
78
|
+
progressGroup?: string;
|
|
67
79
|
};
|
|
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
|
+
}
|
|
68
91
|
type TldrModelPreference = { provider: string; id: string };
|
|
69
92
|
type FastModelAuth = {
|
|
70
93
|
model: Parameters<typeof complete>[0];
|
|
@@ -88,6 +111,12 @@ const TLDR_MAX_TOKENS = 120;
|
|
|
88
111
|
const TLDR_REQUEST_TIMEOUT_MS = 2_000;
|
|
89
112
|
const TLDR_DISPLAY_UPDATE_INTERVAL_MS = 1_200;
|
|
90
113
|
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;
|
|
91
120
|
// Burst debouncing on the generation side: coalesce rapid-fire activities into
|
|
92
121
|
// one model call. Quiet window catches short bursts; max wait surfaces progress
|
|
93
122
|
// during continuous activity.
|
|
@@ -448,13 +477,58 @@ function extractTextContent(content: readonly unknown[] | undefined): string | u
|
|
|
448
477
|
return compacted.length > 0 ? compacted : undefined;
|
|
449
478
|
}
|
|
450
479
|
|
|
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
|
+
|
|
451
516
|
class TldrFactCollector {
|
|
452
517
|
private nextIndex = 1;
|
|
453
518
|
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>();
|
|
454
523
|
|
|
455
524
|
resetConversation(): void {
|
|
456
525
|
this.nextIndex = 1;
|
|
457
526
|
this.activities.splice(0);
|
|
527
|
+
this.activeToolInputAnchors.clear();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
latestActivity(): TldrActivity | undefined {
|
|
531
|
+
return this.activities[this.activities.length - 1];
|
|
458
532
|
}
|
|
459
533
|
|
|
460
534
|
recordUserMessage(prompt: string): TldrActivity {
|
|
@@ -486,6 +560,79 @@ class TldrFactCollector {
|
|
|
486
560
|
);
|
|
487
561
|
}
|
|
488
562
|
|
|
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
|
+
|
|
489
636
|
recordToolCall(event: {
|
|
490
637
|
toolName: string;
|
|
491
638
|
input?: Record<string, unknown>;
|
|
@@ -499,6 +646,45 @@ class TldrFactCollector {
|
|
|
499
646
|
);
|
|
500
647
|
}
|
|
501
648
|
|
|
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
|
+
|
|
502
688
|
recordToolResult(event: {
|
|
503
689
|
toolName: string;
|
|
504
690
|
input?: Record<string, unknown>;
|
|
@@ -517,15 +703,24 @@ class TldrFactCollector {
|
|
|
517
703
|
const status = isError ? "error" : "ok";
|
|
518
704
|
const tool = compactToolInput(event.toolName, event.input);
|
|
519
705
|
|
|
520
|
-
//
|
|
521
|
-
//
|
|
706
|
+
// The completed result fact subsumes all in-progress facts for the same
|
|
707
|
+
// call: the original tool_call, any tool_input_* lifecycle facts, and any
|
|
708
|
+
// tool_execution_update progress. The model only needs the final outcome.
|
|
522
709
|
if (event.toolCallId) {
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
710
|
+
const id = event.toolCallId;
|
|
711
|
+
for (let i = this.activities.length - 1; i >= 0; i--) {
|
|
712
|
+
const activity = this.activities[i];
|
|
713
|
+
if (activity.toolCallId !== id) continue;
|
|
714
|
+
if (
|
|
715
|
+
activity.activityType === "tool_call" ||
|
|
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
|
+
}
|
|
529
724
|
}
|
|
530
725
|
|
|
531
726
|
return this.addActivity(
|
|
@@ -593,13 +788,15 @@ class TldrFactCollector {
|
|
|
593
788
|
displayPriority: TldrDisplayPriority,
|
|
594
789
|
text: string,
|
|
595
790
|
toolCallId?: string,
|
|
791
|
+
progressGroup?: string,
|
|
596
792
|
): TldrActivity {
|
|
597
|
-
const activity = {
|
|
793
|
+
const activity: TldrActivity = {
|
|
598
794
|
index: this.nextIndex,
|
|
599
795
|
activityType,
|
|
600
796
|
displayPriority,
|
|
601
797
|
text: truncateText(text, MAX_ACTIVITY_TEXT_CHARS),
|
|
602
798
|
toolCallId,
|
|
799
|
+
progressGroup,
|
|
603
800
|
};
|
|
604
801
|
this.nextIndex++;
|
|
605
802
|
this.activities.push(activity);
|
|
@@ -631,6 +828,9 @@ class FooterTldrEngine {
|
|
|
631
828
|
private pendingNormalCheckpoint?: TldrCheckpointJob;
|
|
632
829
|
private normalCheckpointTimer?: ReturnType<typeof setTimeout>;
|
|
633
830
|
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;
|
|
634
834
|
|
|
635
835
|
constructor(private readonly requestRender: () => void) {}
|
|
636
836
|
|
|
@@ -655,7 +855,22 @@ class FooterTldrEngine {
|
|
|
655
855
|
this.enqueue(ctx, this.facts.recordUserMessage(prompt));
|
|
656
856
|
}
|
|
657
857
|
|
|
658
|
-
recordAssistantUpdate(
|
|
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
|
+
|
|
659
874
|
const activity = this.facts.recordAssistantUpdate(message);
|
|
660
875
|
if (activity) this.enqueue(ctx, activity);
|
|
661
876
|
}
|
|
@@ -667,6 +882,17 @@ class FooterTldrEngine {
|
|
|
667
882
|
this.enqueue(ctx, this.facts.recordToolCall(event));
|
|
668
883
|
}
|
|
669
884
|
|
|
885
|
+
recordToolExecutionUpdate(
|
|
886
|
+
ctx: ExtensionContext,
|
|
887
|
+
event: {
|
|
888
|
+
toolCallId: string;
|
|
889
|
+
toolName: string;
|
|
890
|
+
partialResult: unknown;
|
|
891
|
+
},
|
|
892
|
+
): void {
|
|
893
|
+
this.enqueue(ctx, this.facts.recordToolExecutionUpdate(event));
|
|
894
|
+
}
|
|
895
|
+
|
|
670
896
|
recordToolResult(
|
|
671
897
|
ctx: ExtensionContext,
|
|
672
898
|
event: {
|
|
@@ -701,6 +927,7 @@ class FooterTldrEngine {
|
|
|
701
927
|
this.lastDisplayAt = Number.NEGATIVE_INFINITY;
|
|
702
928
|
this.acceptedCheckpoints.splice(0);
|
|
703
929
|
this.pendingDisplayCheckpoint = undefined;
|
|
930
|
+
this.lastRenderedProgressGroup = undefined;
|
|
704
931
|
this.currentText = null;
|
|
705
932
|
this.requestRender();
|
|
706
933
|
}
|
|
@@ -769,10 +996,12 @@ class FooterTldrEngine {
|
|
|
769
996
|
|
|
770
997
|
private enqueue(ctx: ExtensionContext, activity: TldrActivity): void {
|
|
771
998
|
if (!ctx.hasUI) return;
|
|
772
|
-
const job = {
|
|
999
|
+
const job: TldrCheckpointJob = {
|
|
773
1000
|
activityIndex: activity.index,
|
|
1001
|
+
activityType: activity.activityType,
|
|
774
1002
|
displayPriority: activity.displayPriority,
|
|
775
1003
|
runId: this.runId,
|
|
1004
|
+
progressGroup: activity.progressGroup,
|
|
776
1005
|
};
|
|
777
1006
|
|
|
778
1007
|
if (job.displayPriority === "immediate") {
|
|
@@ -806,9 +1035,13 @@ class FooterTldrEngine {
|
|
|
806
1035
|
this.scheduleNormalCheckpoint(ctx, job);
|
|
807
1036
|
}
|
|
808
1037
|
|
|
809
|
-
private
|
|
1038
|
+
private clearDisplayTimer(): void {
|
|
810
1039
|
if (this.displayTimer) clearTimeout(this.displayTimer);
|
|
811
1040
|
this.displayTimer = undefined;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
private clearPendingDisplay(): void {
|
|
1044
|
+
this.clearDisplayTimer();
|
|
812
1045
|
this.pendingDisplayCheckpoint = undefined;
|
|
813
1046
|
}
|
|
814
1047
|
|
|
@@ -866,10 +1099,12 @@ class FooterTldrEngine {
|
|
|
866
1099
|
const rawText = extractTextContent(response.content) ?? "";
|
|
867
1100
|
const text = sanitizeTldrText(rawText);
|
|
868
1101
|
if (!text) return;
|
|
869
|
-
const checkpoint = {
|
|
1102
|
+
const checkpoint: TldrCheckpoint = {
|
|
870
1103
|
activityIndex: job.activityIndex,
|
|
1104
|
+
activityType: job.activityType,
|
|
871
1105
|
displayPriority: job.displayPriority,
|
|
872
1106
|
text,
|
|
1107
|
+
progressGroup: job.progressGroup,
|
|
873
1108
|
};
|
|
874
1109
|
this.acceptCheckpoint(checkpoint);
|
|
875
1110
|
this.considerDisplayingCheckpoint(ctx, checkpoint);
|
|
@@ -901,7 +1136,7 @@ class FooterTldrEngine {
|
|
|
901
1136
|
checkpoint: TldrCheckpoint,
|
|
902
1137
|
): void {
|
|
903
1138
|
if (checkpoint.activityIndex <= this.lastRenderedActivityIndex) return;
|
|
904
|
-
if (
|
|
1139
|
+
if (this.shouldDropStaleCheckpoint(checkpoint)) return;
|
|
905
1140
|
|
|
906
1141
|
if (checkpoint.displayPriority !== "normal" || !this.lastRenderedText) {
|
|
907
1142
|
this.clearPendingDisplay();
|
|
@@ -909,22 +1144,66 @@ class FooterTldrEngine {
|
|
|
909
1144
|
return;
|
|
910
1145
|
}
|
|
911
1146
|
|
|
1147
|
+
const displayIntervalMs = this.displayIntervalFor(checkpoint);
|
|
912
1148
|
const elapsedMs = performance.now() - this.lastDisplayAt;
|
|
913
|
-
if (elapsedMs >=
|
|
1149
|
+
if (elapsedMs >= displayIntervalMs) {
|
|
914
1150
|
this.clearPendingDisplay();
|
|
915
1151
|
this.renderCheckpoint(checkpoint);
|
|
916
1152
|
return;
|
|
917
1153
|
}
|
|
918
1154
|
|
|
919
1155
|
this.pendingDisplayCheckpoint = checkpoint;
|
|
920
|
-
|
|
1156
|
+
// Recompute the timer for each newly accepted pending checkpoint. A long
|
|
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();
|
|
921
1160
|
this.displayTimer = setTimeout(() => {
|
|
922
1161
|
this.displayTimer = undefined;
|
|
923
1162
|
const pending = this.pendingDisplayCheckpoint;
|
|
924
1163
|
this.pendingDisplayCheckpoint = undefined;
|
|
925
|
-
if (!pending
|
|
1164
|
+
if (!pending) return;
|
|
1165
|
+
if (this.shouldDropStaleCheckpoint(pending)) return;
|
|
926
1166
|
this.renderCheckpoint(pending);
|
|
927
|
-
},
|
|
1167
|
+
}, displayIntervalMs - elapsedMs);
|
|
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;
|
|
928
1207
|
}
|
|
929
1208
|
|
|
930
1209
|
private renderCheckpoint(checkpoint: TldrCheckpoint): void {
|
|
@@ -933,6 +1212,9 @@ class FooterTldrEngine {
|
|
|
933
1212
|
this.lastRenderedActivityIndex = checkpoint.activityIndex;
|
|
934
1213
|
this.lastRenderedText = checkpoint.text;
|
|
935
1214
|
this.lastDisplayAt = performance.now();
|
|
1215
|
+
this.lastRenderedProgressGroup = isToolProgressActivity(checkpoint.activityType)
|
|
1216
|
+
? checkpoint.progressGroup
|
|
1217
|
+
: undefined;
|
|
936
1218
|
this.currentText = checkpoint.text;
|
|
937
1219
|
this.requestRender();
|
|
938
1220
|
}
|
|
@@ -1001,6 +1283,50 @@ function formatRawActivity(activity: TldrActivity): string {
|
|
|
1001
1283
|
return `- ${activity.text}`;
|
|
1002
1284
|
}
|
|
1003
1285
|
|
|
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
|
+
|
|
1004
1330
|
// Strips ANSI/CSI/OSC/DCS/SOS/PM/APC escape sequences from model output before
|
|
1005
1331
|
// it lands in the terminal. The TLDR text is model-controlled and renders into
|
|
1006
1332
|
// the footer; the system prompt asking for plain text is not a security
|
|
@@ -1418,11 +1744,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
1418
1744
|
tldr.recordUserMessage(ctx, event.prompt);
|
|
1419
1745
|
});
|
|
1420
1746
|
pi.on("message_update", async (event, ctx) => {
|
|
1421
|
-
tldr.recordAssistantUpdate(
|
|
1747
|
+
tldr.recordAssistantUpdate(
|
|
1748
|
+
ctx,
|
|
1749
|
+
event.message,
|
|
1750
|
+
(event as { assistantMessageEvent?: unknown }).assistantMessageEvent,
|
|
1751
|
+
);
|
|
1422
1752
|
});
|
|
1423
1753
|
pi.on("tool_call", async (event, ctx) => {
|
|
1424
1754
|
tldr.recordToolCall(ctx, event);
|
|
1425
1755
|
});
|
|
1756
|
+
pi.on("tool_execution_update", async (event, ctx) => {
|
|
1757
|
+
tldr.recordToolExecutionUpdate(ctx, event);
|
|
1758
|
+
});
|
|
1426
1759
|
pi.on("tool_result", async (event, ctx) => {
|
|
1427
1760
|
tldr.recordToolResult(ctx, event);
|
|
1428
1761
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-bar",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
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.25/assets/screenshot-yellow.png"
|
|
55
55
|
}
|
|
56
56
|
}
|