pi-cc-extensions 0.8.9 → 0.8.11

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.
Files changed (42) hide show
  1. package/README.md +3 -2
  2. package/extensions/agent-autocomplete.ts +11 -16
  3. package/extensions/ask-user-question/ask-user-question.ts +29 -7
  4. package/extensions/ask-user-question/config.ts +6 -2
  5. package/extensions/ask-user-question/index.ts +8 -8
  6. package/extensions/ask-user-question/rpc-fallback.ts +16 -4
  7. package/extensions/ask-user-question/state/build-questionnaire.ts +16 -5
  8. package/extensions/ask-user-question/state/key-router.ts +29 -7
  9. package/extensions/ask-user-question/state/questionnaire-session.ts +1 -4
  10. package/extensions/ask-user-question/state/selectors/projections.ts +4 -1
  11. package/extensions/ask-user-question/state/state-reducer.ts +24 -7
  12. package/extensions/ask-user-question/tool/response-envelope.ts +4 -1
  13. package/extensions/ask-user-question/tool/types.ts +5 -1
  14. package/extensions/ask-user-question/tool/validate-questionnaire.ts +10 -2
  15. package/extensions/ask-user-question/view/components/inline-input.ts +15 -2
  16. package/extensions/ask-user-question/view/components/multi-select-view.ts +11 -4
  17. package/extensions/ask-user-question/view/components/option-list-view.ts +14 -5
  18. package/extensions/ask-user-question/view/components/preview/preview-block-renderer.ts +13 -7
  19. package/extensions/ask-user-question/view/components/preview/preview-layout-decider.ts +16 -4
  20. package/extensions/ask-user-question/view/components/preview/preview-pane.ts +5 -1
  21. package/extensions/ask-user-question/view/components/submit-picker.ts +3 -1
  22. package/extensions/ask-user-question/view/components/wrapping-select.ts +20 -4
  23. package/extensions/ask-user-question/view/dialog-builder.ts +27 -7
  24. package/extensions/ask-user-question/view/tab-content-strategy.ts +23 -8
  25. package/extensions/claude-code-style.ts +1722 -1485
  26. package/extensions/compact-style.ts +120 -43
  27. package/extensions/context.ts +502 -394
  28. package/extensions/session-reference-core.ts +8 -3
  29. package/extensions/session-reference.ts +475 -448
  30. package/extensions/tool-diff/ATTRIBUTION.md +28 -0
  31. package/extensions/tool-diff/ansi-utils.ts +113 -0
  32. package/extensions/tool-diff/diff-presentation.ts +70 -0
  33. package/extensions/tool-diff/diff-renderer.ts +2827 -0
  34. package/extensions/tool-diff/index.ts +106 -0
  35. package/extensions/tool-diff/line-width-safety.ts +91 -0
  36. package/extensions/tool-diff/render-utils.ts +5 -0
  37. package/extensions/tool-diff/types.ts +20 -0
  38. package/extensions/tool-diff/write-display-utils.ts +29 -0
  39. package/extensions/tool-diff/write-execution.ts +113 -0
  40. package/extensions/tool-result-sanitize.ts +4 -4
  41. package/package.json +64 -61
  42. package/themes/github-dark-default.json +91 -91
@@ -298,7 +298,10 @@ export function resultPreview(result: any, isPartial = false): string {
298
298
  ? result.content.find((c: any) => c?.type === "text" && typeof c.text === "string")?.text
299
299
  : undefined;
300
300
  if (!text) return isPartial ? "running" : "";
301
- const lines = sanitizeToolResultText(text).trim().split("\n").filter((line) => line.trim().length > 0);
301
+ const lines = sanitizeToolResultText(text)
302
+ .trim()
303
+ .split("\n")
304
+ .filter((line) => line.trim().length > 0);
302
305
  if (lines.length === 0) return isPartial ? "running" : "";
303
306
  if (lines.length === 1) return lines[0];
304
307
  return `${lines.length} lines`;
@@ -399,7 +402,10 @@ function statusMarker(
399
402
  if (opts.isError) return theme.fg("error", "✗ ");
400
403
  if (opts.running) {
401
404
  if (!CLAUDE_LOADING_TOOLS.has(name)) return theme.fg("accent", "● ");
402
- const frame = CLAUDE_LOADING_FRAMES[Math.floor(Date.now() / BLINK_INTERVAL_MS) % CLAUDE_LOADING_FRAMES.length] ?? "⠋";
405
+ const frame =
406
+ CLAUDE_LOADING_FRAMES[
407
+ Math.floor(Date.now() / BLINK_INTERVAL_MS) % CLAUDE_LOADING_FRAMES.length
408
+ ] ?? "⠋";
403
409
  return theme.fg("accent", `${frame} `);
404
410
  }
405
411
  if (opts.hasResult) return theme.fg("success", "✓ ");
@@ -409,10 +415,16 @@ function statusMarker(
409
415
  function textSignalHasVisibleContent(assistantMessageEvent: any): boolean {
410
416
  const type = assistantMessageEvent?.type;
411
417
  if (type === "text_delta") {
412
- return typeof assistantMessageEvent.delta === "string" && assistantMessageEvent.delta.trim().length > 0;
418
+ return (
419
+ typeof assistantMessageEvent.delta === "string" &&
420
+ assistantMessageEvent.delta.trim().length > 0
421
+ );
413
422
  }
414
423
  if (type === "text_end") {
415
- return typeof assistantMessageEvent.content === "string" && assistantMessageEvent.content.trim().length > 0;
424
+ return (
425
+ typeof assistantMessageEvent.content === "string" &&
426
+ assistantMessageEvent.content.trim().length > 0
427
+ );
416
428
  }
417
429
  return false;
418
430
  }
@@ -492,7 +504,8 @@ function updateCurrentThoughtFromMessage(message: any) {
492
504
  }
493
505
 
494
506
  function anchorCurrentThoughtTo(info: ToolInfo) {
495
- if (!thoughtTickerEnabled() || !state.currentThoughtHeading || state.thoughtAnchorId === info.id) return;
507
+ if (!thoughtTickerEnabled() || !state.currentThoughtHeading || state.thoughtAnchorId === info.id)
508
+ return;
496
509
  const previousAnchorId = state.thoughtAnchorId;
497
510
  state.thoughtAnchorId = info.id;
498
511
  invalidateToolById(previousAnchorId);
@@ -500,10 +513,18 @@ function anchorCurrentThoughtTo(info: ToolInfo) {
500
513
  }
501
514
 
502
515
  function currentThoughtLine(toolCallId: string, theme: Theme): string {
503
- if (!thoughtTickerEnabled() || state.thoughtAnchorId !== toolCallId || !state.currentThoughtHeading) return "";
516
+ if (
517
+ !thoughtTickerEnabled() ||
518
+ state.thoughtAnchorId !== toolCallId ||
519
+ !state.currentThoughtHeading
520
+ )
521
+ return "";
504
522
  const prefix = " ↳ ";
505
523
  const budget = previewWidth((process.stdout.columns || 100) - prefix.length);
506
- return theme.fg("dim", prefix) + theme.fg("thinkingText", limitPlain(state.currentThoughtHeading, budget));
524
+ return (
525
+ theme.fg("dim", prefix) +
526
+ theme.fg("thinkingText", limitPlain(state.currentThoughtHeading, budget))
527
+ );
507
528
  }
508
529
 
509
530
  function upsertToolInfo(id: string, name: string, args: any, invalidate?: () => void): ToolInfo {
@@ -542,7 +563,10 @@ function joinBurst(info: ToolInfo) {
542
563
  }
543
564
 
544
565
  // Only consecutive uses of the same tool are grouped.
545
- if (state.currentBurst.length && state.currentBurst[state.currentBurst.length - 1].name !== info.name) {
566
+ if (
567
+ state.currentBurst.length &&
568
+ state.currentBurst[state.currentBurst.length - 1].name !== info.name
569
+ ) {
546
570
  state.currentBurst = [];
547
571
  }
548
572
 
@@ -592,7 +616,10 @@ function updateToolResult(toolCallId: string, result: any, isError = false, isPa
592
616
  info.running = false;
593
617
  if (info.startedAt) info.durationMs = Date.now() - info.startedAt;
594
618
  if (state.currentBurst.includes(info) && state.currentBurst.length > 1) {
595
- info.burstDurationMs = state.currentBurst.reduce((total, tool) => total + (tool.durationMs ?? 0), 0);
619
+ info.burstDurationMs = state.currentBurst.reduce(
620
+ (total, tool) => total + (tool.durationMs ?? 0),
621
+ 0,
622
+ );
596
623
  }
597
624
  if (isError) state.runStats.failedCount++;
598
625
  }
@@ -616,7 +643,9 @@ function compactToolLine(
616
643
  if (info.hidden) return "";
617
644
 
618
645
  const isBurst = (info.burstCount ?? 1) > 1;
619
- const durationText = formatDuration((isBurst ? (info.burstDurationMs ?? info.durationMs) : info.durationMs) ?? 0);
646
+ const durationText = formatDuration(
647
+ (isBurst ? (info.burstDurationMs ?? info.durationMs) : info.durationMs) ?? 0,
648
+ );
620
649
  const inner = [info.result ? oneLine(info.result) : "", durationText].filter(Boolean).join(" · ");
621
650
  const status = inner ? ` {${inner}}` : info.running ? " {running}" : "";
622
651
  const details = `${info.preview}${status}`;
@@ -629,7 +658,9 @@ function compactToolLine(
629
658
  if (!isBurst) return indent + marker + theme.fg("muted", limitPlain(details));
630
659
 
631
660
  const prefix = `${info.burstCount}× `;
632
- const budget = previewWidth((process.stdout.columns || 100) - indent.length - prefix.length - MARKER_WIDTH);
661
+ const budget = previewWidth(
662
+ (process.stdout.columns || 100) - indent.length - prefix.length - MARKER_WIDTH,
663
+ );
633
664
  return indent + marker + theme.fg("muted", prefix + limitPlain(details, budget));
634
665
  }
635
666
 
@@ -662,20 +693,27 @@ function nativeToolShellChild(component: any): any {
662
693
  }
663
694
 
664
695
  function toolShellChildren(component: any): any[] {
665
- return [component.contentText, component.contentBox, component.selfRenderContainer].filter(Boolean);
696
+ return [component.contentText, component.contentBox, component.selfRenderContainer].filter(
697
+ Boolean,
698
+ );
666
699
  }
667
700
 
668
701
  function syncToolShellChild(component: any, target: any, preferredIndex?: number): void {
669
702
  if (!target || !Array.isArray(component.children)) return;
670
703
  const candidates = new Set(toolShellChildren(component));
671
704
  const shellIndexes = component.children
672
- .map((child: any, index: number) => candidates.has(child) ? index : -1)
705
+ .map((child: any, index: number) => (candidates.has(child) ? index : -1))
673
706
  .filter((index: number) => index >= 0);
674
- const targetIndex = preferredIndex !== undefined && shellIndexes.includes(preferredIndex)
675
- ? preferredIndex
676
- : shellIndexes[0];
707
+ const targetIndex =
708
+ preferredIndex !== undefined && shellIndexes.includes(preferredIndex)
709
+ ? preferredIndex
710
+ : shellIndexes[0];
677
711
  if (targetIndex === undefined) {
678
- if (preferredIndex !== undefined && preferredIndex >= 0 && preferredIndex <= component.children.length) {
712
+ if (
713
+ preferredIndex !== undefined &&
714
+ preferredIndex >= 0 &&
715
+ preferredIndex <= component.children.length
716
+ ) {
679
717
  component.children.splice(preferredIndex, 0, target);
680
718
  } else if (typeof component.addChild === "function") {
681
719
  component.addChild(target);
@@ -706,9 +744,12 @@ function restoreNativeToolShell(component: any): void {
706
744
  delete component[TOOL_NATIVE_CHILD_KEY];
707
745
  }
708
746
 
709
- function patchToolExecutionComponent(installation: CompactInstallation): ToolPrototypePatch | undefined {
747
+ function patchToolExecutionComponent(
748
+ installation: CompactInstallation,
749
+ ): ToolPrototypePatch | undefined {
710
750
  const prototype = ToolExecutionComponent.prototype as any;
711
- if (typeof prototype.updateDisplay !== "function" || typeof prototype.render !== "function") return undefined;
751
+ if (typeof prototype.updateDisplay !== "function" || typeof prototype.render !== "function")
752
+ return undefined;
712
753
 
713
754
  const patch: ToolPrototypePatch = {
714
755
  prototype,
@@ -739,7 +780,12 @@ function patchToolExecutionComponent(installation: CompactInstallation): ToolPro
739
780
  }
740
781
  return patch.originalUpdateDisplay.call(this);
741
782
  }
742
- if (!this.toolCallId || !this.toolName || !this.selfRenderContainer || typeof this.selfRenderContainer.clear !== "function") {
783
+ if (
784
+ !this.toolCallId ||
785
+ !this.toolName ||
786
+ !this.selfRenderContainer ||
787
+ typeof this.selfRenderContainer.clear !== "function"
788
+ ) {
743
789
  this.__compactStyleForceSelf = false;
744
790
  this.__compactStyleHidden = false;
745
791
  restoreNativeToolShell(this);
@@ -817,27 +863,39 @@ function appendNativeStopReason(component: any, message: any, theme: Theme): voi
817
863
  component.hasToolCalls = hasToolCalls;
818
864
  if (message.stopReason === "length") {
819
865
  component.contentContainer.addChild(new Spacer(1));
820
- component.contentContainer.addChild(new Text(
821
- theme.fg("error", "Error: Model stopped because it reached the maximum output token limit. The response may be incomplete."),
822
- component.outputPad,
823
- 0,
824
- ));
866
+ component.contentContainer.addChild(
867
+ new Text(
868
+ theme.fg(
869
+ "error",
870
+ "Error: Model stopped because it reached the maximum output token limit. The response may be incomplete.",
871
+ ),
872
+ component.outputPad,
873
+ 0,
874
+ ),
875
+ );
825
876
  } else if (!hasToolCalls) {
826
877
  if (message.stopReason === "aborted") {
827
- const abortMessage = message.errorMessage && message.errorMessage !== "Request was aborted"
828
- ? message.errorMessage
829
- : "Operation aborted";
878
+ const abortMessage =
879
+ message.errorMessage && message.errorMessage !== "Request was aborted"
880
+ ? message.errorMessage
881
+ : "Operation aborted";
830
882
  component.contentContainer.addChild(new Spacer(1));
831
- component.contentContainer.addChild(new Text(theme.fg("error", abortMessage), component.outputPad, 0));
883
+ component.contentContainer.addChild(
884
+ new Text(theme.fg("error", abortMessage), component.outputPad, 0),
885
+ );
832
886
  } else if (message.stopReason === "error") {
833
887
  const errorMessage = message.errorMessage || "Unknown error";
834
888
  component.contentContainer.addChild(new Spacer(1));
835
- component.contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), component.outputPad, 0));
889
+ component.contentContainer.addChild(
890
+ new Text(theme.fg("error", `Error: ${errorMessage}`), component.outputPad, 0),
891
+ );
836
892
  }
837
893
  }
838
894
  }
839
895
 
840
- function patchAssistantMessageComponent(installation: CompactInstallation): AssistantPrototypePatch | undefined {
896
+ function patchAssistantMessageComponent(
897
+ installation: CompactInstallation,
898
+ ): AssistantPrototypePatch | undefined {
841
899
  const prototype = AssistantMessageComponent.prototype as any;
842
900
  if (typeof prototype.updateContent !== "function") return undefined;
843
901
 
@@ -884,14 +942,19 @@ function patchAssistantMessageComponent(installation: CompactInstallation): Assi
884
942
  // text and stop-reason paths exactly, while intentionally omitting thinking.
885
943
  this.lastMessage = message;
886
944
  this.contentContainer.clear();
887
- const texts = message.content.filter((content: any) => content.type === "text" && content.text?.trim());
945
+ const texts = message.content.filter(
946
+ (content: any) => content.type === "text" && content.text?.trim(),
947
+ );
888
948
  const hasToolCalls = message.content.some((content: any) => content.type === "toolCall");
889
- const hasTerminalNotice = message.stopReason === "length"
890
- || (!hasToolCalls && (message.stopReason === "aborted" || message.stopReason === "error"));
949
+ const hasTerminalNotice =
950
+ message.stopReason === "length" ||
951
+ (!hasToolCalls && (message.stopReason === "aborted" || message.stopReason === "error"));
891
952
  if (texts.length > 0) {
892
953
  this.contentContainer.addChild(new Spacer(1));
893
954
  for (const content of texts) {
894
- this.contentContainer.addChild(new Markdown(content.text.trim(), this.outputPad, 0, this.markdownTheme));
955
+ this.contentContainer.addChild(
956
+ new Markdown(content.text.trim(), this.outputPad, 0, this.markdownTheme),
957
+ );
895
958
  }
896
959
  }
897
960
  if (texts.length > 0 || hasTerminalNotice) {
@@ -932,7 +995,8 @@ function restorePrototypePatches(installation: CompactInstallation): void {
932
995
  if (toolPatch.prototype.render === toolPatch.patchedRender) {
933
996
  toolPatch.prototype.render = toolPatch.originalRender;
934
997
  }
935
- if (toolPatch.prototype[TOOL_PATCH_KEY] === toolPatch) delete toolPatch.prototype[TOOL_PATCH_KEY];
998
+ if (toolPatch.prototype[TOOL_PATCH_KEY] === toolPatch)
999
+ delete toolPatch.prototype[TOOL_PATCH_KEY];
936
1000
  }
937
1001
 
938
1002
  const assistantPatch = installation.assistantPatch;
@@ -973,7 +1037,8 @@ export function refreshCompactTranscript(): void {
973
1037
 
974
1038
  export function normalizeSummary(input: unknown): CompactSummaryData {
975
1039
  const source = (input && typeof input === "object" ? input : {}) as Record<string, unknown>;
976
- const num = (value: unknown) => (typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0);
1040
+ const num = (value: unknown) =>
1041
+ typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
977
1042
  return {
978
1043
  reads: num(source.reads),
979
1044
  edits: num(source.edits),
@@ -1020,9 +1085,10 @@ export class DynamicSummaryComponent {
1020
1085
  render(width: number): string[] {
1021
1086
  const line = currentMode() !== "off" ? summaryLine(this.data) : "";
1022
1087
  if (!line) return [];
1023
- const italic = typeof (this.theme as any).italic === "function"
1024
- ? (this.theme as any).italic(this.theme.fg("muted", line))
1025
- : this.theme.fg("muted", line);
1088
+ const italic =
1089
+ typeof (this.theme as any).italic === "function"
1090
+ ? (this.theme as any).italic(this.theme.fg("muted", line))
1091
+ : this.theme.fg("muted", line);
1026
1092
  const prefix = this.theme.fg("borderMuted", "│ ");
1027
1093
  return new Text(prefix + italic, 0, 0).render(width);
1028
1094
  }
@@ -1045,6 +1111,7 @@ function appendRunSummary() {
1045
1111
 
1046
1112
  export type CompactStyleHooks = {
1047
1113
  onSessionStart(event: unknown, ctx: ExtensionContext): void;
1114
+ onSessionCompact(event: unknown, ctx: ExtensionContext): void;
1048
1115
  onSessionShutdown(event: unknown, ctx: ExtensionContext): void;
1049
1116
  onAgentStart(event: unknown, ctx: ExtensionContext): void;
1050
1117
  onAgentEnd(event: unknown, ctx: ExtensionContext): void;
@@ -1099,8 +1166,9 @@ export function installCompactStyle(pi: ExtensionAPI, host: CompactStyleHost): C
1099
1166
  try {
1100
1167
  patchRenderers(installation);
1101
1168
  if (typeof (pi as any).registerEntryRenderer === "function") {
1102
- pi.registerEntryRenderer(SUMMARY_ENTRY_TYPE, (entry: any, _options: any, theme: Theme) =>
1103
- new DynamicSummaryComponent(entry.data, theme),
1169
+ pi.registerEntryRenderer(
1170
+ SUMMARY_ENTRY_TYPE,
1171
+ (entry: any, _options: any, theme: Theme) => new DynamicSummaryComponent(entry.data, theme),
1104
1172
  );
1105
1173
  }
1106
1174
  } catch (error) {
@@ -1121,6 +1189,14 @@ export function installCompactStyle(pi: ExtensionAPI, host: CompactStyleHost): C
1121
1189
  refreshCompactTranscript();
1122
1190
  ctx.ui.setStatus(STATUS_KEY, undefined);
1123
1191
  },
1192
+ onSessionCompact: (_event, ctx) => {
1193
+ if (!isOwner()) return;
1194
+ // Pi rebuilds transcript components after compaction without emitting
1195
+ // session_start. Reclaim renderer ownership and repaint retained rows.
1196
+ ensureAssistantPatchOwnership(installation);
1197
+ captureTheme(ctx);
1198
+ refreshCompactTranscript();
1199
+ },
1124
1200
  onSessionShutdown: (_event, ctx) => {
1125
1201
  if (isOwner()) teardownCompactInstallation(installation);
1126
1202
  ctx.ui.setStatus(STATUS_KEY, undefined);
@@ -1150,7 +1226,8 @@ export function installCompactStyle(pi: ExtensionAPI, host: CompactStyleHost): C
1150
1226
  ensureAssistantPatchOwnership(installation);
1151
1227
  captureTheme(ctx);
1152
1228
  const type = event?.assistantMessageEvent?.type;
1153
- if (typeof type === "string" && type.startsWith("thinking_")) updateCurrentThoughtFromMessage(event.message);
1229
+ if (typeof type === "string" && type.startsWith("thinking_"))
1230
+ updateCurrentThoughtFromMessage(event.message);
1154
1231
  if (textSignalHasVisibleContent(event?.assistantMessageEvent)) {
1155
1232
  clearCurrentThought();
1156
1233
  state.currentBurst = [];