pi-bar 0.3.19 → 0.3.21

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 CHANGED
@@ -9,7 +9,7 @@
9
9
  claude-opus-4.7 ❯ think:med ❯ 2.6% / 1.0M ❯ Inspecting package structure ❯ plan:active
10
10
  ```
11
11
 
12
- ![pi-bar screenshot, three rows showing green, yellow, and red context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.19/assets/screenshot.png)
12
+ ![pi-bar screenshot, three rows showing green, yellow, and red context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.21/assets/screenshot.png)
13
13
 
14
14
  ## Why use it?
15
15
 
@@ -53,6 +53,7 @@ type TldrActivity = {
53
53
  activityType: TldrActivityType;
54
54
  displayPriority: TldrDisplayPriority;
55
55
  text: string;
56
+ toolCallId?: string;
56
57
  };
57
58
  type TldrCheckpoint = {
58
59
  activityIndex: number;
@@ -365,12 +366,18 @@ function usefulLineCount(text: string): number {
365
366
  .filter(Boolean).length;
366
367
  }
367
368
 
369
+ const ERROR_LINE_PATTERN = /(error|failed|TS\d+|E[A-Z]+\d*|Command exited|denied|refused|aborted|panic|fatal|exception)/i;
370
+
368
371
  function errorSummary(text: string | undefined): string | undefined {
369
372
  if (!text) return undefined;
370
- const compacted = compactText(text);
371
- const exitMatch = compacted.match(/Command exited with code \d+/);
372
- if (exitMatch) return truncateText(exitMatch[0], MAX_TOOL_RESULT_ERROR_CHARS);
373
- return truncateText(compacted, MAX_TOOL_RESULT_ERROR_CHARS);
373
+ const cleaned = text.replace(ANSI_PATTERN, "");
374
+ const lines = cleaned
375
+ .split("\n")
376
+ .map((line) => line.trim())
377
+ .filter(Boolean);
378
+ if (lines.length === 0) return undefined;
379
+ const matched = lines.find((line) => ERROR_LINE_PATTERN.test(line)) ?? lines[lines.length - 1];
380
+ return truncateText(compactText(matched), MAX_TOOL_RESULT_ERROR_CHARS);
374
381
  }
375
382
 
376
383
  function summarizeToolResult(
@@ -442,20 +449,32 @@ class TldrFactCollector {
442
449
  const record = message as Record<string, unknown>;
443
450
  if (record.role !== "assistant") return undefined;
444
451
  const text = extractTextContent(record.content as readonly unknown[] | undefined);
445
- return text
446
- ? this.addActivity(
447
- "assistant_update",
448
- "normal",
449
- `assistant: ${tailText(text, MAX_ASSISTANT_UPDATE_CHARS)}`,
450
- )
451
- : undefined;
452
+ if (!text) return undefined;
453
+
454
+ // Streaming emits many partial updates per message. Keep only the latest by
455
+ // dropping the previous assistant_update so the model sees one accurate snapshot.
456
+ const last = this.activities[this.activities.length - 1];
457
+ if (last?.activityType === "assistant_update") {
458
+ this.activities.pop();
459
+ }
460
+
461
+ return this.addActivity(
462
+ "assistant_update",
463
+ "normal",
464
+ `assistant: ${tailText(text, MAX_ASSISTANT_UPDATE_CHARS)}`,
465
+ );
452
466
  }
453
467
 
454
- recordToolCall(event: { toolName: string; input?: Record<string, unknown> }): TldrActivity {
468
+ recordToolCall(event: {
469
+ toolName: string;
470
+ input?: Record<string, unknown>;
471
+ toolCallId?: string;
472
+ }): TldrActivity {
455
473
  return this.addActivity(
456
474
  "tool_call",
457
475
  "normal",
458
476
  `tool: ${compactToolInput(event.toolName, event.input)}`,
477
+ event.toolCallId,
459
478
  );
460
479
  }
461
480
 
@@ -465,6 +484,7 @@ class TldrFactCollector {
465
484
  isError?: boolean;
466
485
  content?: readonly unknown[];
467
486
  details?: unknown;
487
+ toolCallId?: string;
468
488
  }): TldrActivity {
469
489
  const isError = Boolean(event.isError);
470
490
  const resultText = summarizeToolResult(
@@ -475,12 +495,25 @@ class TldrFactCollector {
475
495
  );
476
496
  const status = isError ? "error" : "ok";
477
497
  const tool = compactToolInput(event.toolName, event.input);
498
+
499
+ // Result fact subsumes the prior tool_call fact for the same call. Drop the
500
+ // matching tool_call so the model sees one combined fact instead of two.
501
+ if (event.toolCallId) {
502
+ const idx = this.activities.findIndex(
503
+ (activity) =>
504
+ activity.activityType === "tool_call" &&
505
+ activity.toolCallId === event.toolCallId,
506
+ );
507
+ if (idx !== -1) this.activities.splice(idx, 1);
508
+ }
509
+
478
510
  return this.addActivity(
479
511
  "tool_result",
480
512
  "normal",
481
513
  resultText
482
514
  ? `result: ${tool} ${status}; ${resultText}`
483
515
  : `result: ${tool} ${status}`,
516
+ event.toolCallId,
484
517
  );
485
518
  }
486
519
 
@@ -538,12 +571,14 @@ class TldrFactCollector {
538
571
  activityType: TldrActivityType,
539
572
  displayPriority: TldrDisplayPriority,
540
573
  text: string,
574
+ toolCallId?: string,
541
575
  ): TldrActivity {
542
576
  const activity = {
543
577
  index: this.nextIndex,
544
578
  activityType,
545
579
  displayPriority,
546
580
  text: truncateText(text, MAX_ACTIVITY_TEXT_CHARS),
581
+ toolCallId,
547
582
  };
548
583
  this.nextIndex++;
549
584
  this.activities.push(activity);
@@ -600,7 +635,7 @@ class FooterTldrEngine {
600
635
 
601
636
  recordToolCall(
602
637
  ctx: ExtensionContext,
603
- event: { toolName: string; input?: Record<string, unknown> },
638
+ event: { toolName: string; input?: Record<string, unknown>; toolCallId?: string },
604
639
  ): void {
605
640
  this.enqueue(ctx, this.facts.recordToolCall(event));
606
641
  }
@@ -613,6 +648,7 @@ class FooterTldrEngine {
613
648
  isError?: boolean;
614
649
  content?: readonly unknown[];
615
650
  details?: unknown;
651
+ toolCallId?: string;
616
652
  },
617
653
  ): void {
618
654
  this.enqueue(ctx, this.facts.recordToolResult(event));
@@ -741,7 +777,8 @@ class FooterTldrEngine {
741
777
  );
742
778
  if (!this.isCurrentJob(job) || response.stopReason !== "stop") return;
743
779
 
744
- const text = extractTextContent(response.content) ?? "";
780
+ const rawText = extractTextContent(response.content) ?? "";
781
+ const text = sanitizeTldrText(rawText);
745
782
  if (!text) return;
746
783
  const checkpoint = {
747
784
  activityIndex: job.activityIndex,
@@ -826,13 +863,13 @@ class FooterTldrEngine {
826
863
  {
827
864
  type: "text",
828
865
  text: [
829
- "Previous generated TLDR checkpoints:",
866
+ "Prior TLDRs (context only, do not copy phrasing):",
830
867
  previousCheckpointLines(this.acceptedCheckpoints),
831
868
  "",
832
- "New raw activity since the latest accepted checkpoint:",
869
+ "New activity to summarize:",
833
870
  ...rawActivities.map(formatRawActivity),
834
871
  "",
835
- `Write the next TLDR through activity ${job.activityIndex}.`,
872
+ "Write the next TLDR.",
836
873
  ].join("\n"),
837
874
  },
838
875
  ],
@@ -848,14 +885,16 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
848
885
  : "Start with a present-tense -ing verb.";
849
886
  return `Write one plain-English TLDR for a terminal coding agent.
850
887
  Use the prior TLDRs for context and the new activity for the update.
851
- Summarize only activity up to the requested index.
888
+ Summarize the current state of work; do not narrate the history.
852
889
  If context is sparse, still summarize the available activity.
853
890
  Never ask for more information or say there is not enough context.
854
891
  Return one concise status fragment under ${TLDR_TARGET_SUMMARY_CHARS} characters.
855
892
  Omit subjects like "the agent" or "it".
856
893
  Prefer verb + direct object. Include outcome only if important.
857
894
  Do not address the user.
858
- Plain text only; no markdown, JSON, code, bullets, quotes, or file/tool names.
895
+ Do not mention activity numbers, indexes, checkpoints, prior TLDRs, or phrases like "through activity".
896
+ Output only the status fragment itself. No prefixes, labels, bullets, or quotes.
897
+ Plain text only; no markdown, JSON, code, file paths, or tool names.
859
898
  ${tenseInstruction}`;
860
899
  }
861
900
 
@@ -863,12 +902,28 @@ function previousCheckpointLines(checkpoints: readonly TldrCheckpoint[]): string
863
902
  if (checkpoints.length === 0) return "none";
864
903
  return checkpoints
865
904
  .slice(-MAX_CONTEXT_CHECKPOINTS)
866
- .map((checkpoint) => `- Through activity ${checkpoint.activityIndex}: ${checkpoint.text}`)
905
+ .map((checkpoint) => `- ${sanitizeTldrText(checkpoint.text)}`)
867
906
  .join("\n");
868
907
  }
869
908
 
870
909
  function formatRawActivity(activity: TldrActivity): string {
871
- return `[${activity.index}] ${activity.text}`;
910
+ return `- ${activity.text}`;
911
+ }
912
+
913
+ const LEAKED_PREFIX_PATTERN =
914
+ /^\s*(?:[-*•]\s*)?(?:(?:through\s+activity|activity|checkpoint|tldr|summary)\s*[#:\-—–]?\s*\d*[:\.\-—–]?\s*)+/i;
915
+
916
+ const LEADING_PUNCT_PATTERN = /^[\s\-—–•*:#.,;]+/;
917
+
918
+ function sanitizeTldrText(text: string): string {
919
+ let cleaned = text.trim();
920
+ let previous: string;
921
+ do {
922
+ previous = cleaned;
923
+ cleaned = cleaned.replace(LEAKED_PREFIX_PATTERN, "").trim();
924
+ cleaned = cleaned.replace(LEADING_PUNCT_PATTERN, "").trim();
925
+ } while (cleaned !== previous && cleaned.length > 0);
926
+ return cleaned || text.trim();
872
927
  }
873
928
 
874
929
  function shouldShowStatus(key: string, filter: StatusFilter): boolean {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-bar",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
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.19/assets/screenshot.png"
54
+ "image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.21/assets/screenshot.png"
55
55
  }
56
56
  }