@heyhuynhgiabuu/pi-pretty 0.6.26 → 0.6.28

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 (61) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +34 -17
  3. package/dist/config.d.ts +16 -0
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +30 -0
  6. package/dist/config.js.map +1 -1
  7. package/dist/fff.d.ts +16 -1
  8. package/dist/fff.d.ts.map +1 -1
  9. package/dist/fff.js +139 -33
  10. package/dist/fff.js.map +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +70 -24
  13. package/dist/index.js.map +1 -1
  14. package/dist/prompt-editor.d.ts +11 -0
  15. package/dist/prompt-editor.d.ts.map +1 -0
  16. package/dist/prompt-editor.js +78 -0
  17. package/dist/prompt-editor.js.map +1 -0
  18. package/dist/render.d.ts +9 -1
  19. package/dist/render.d.ts.map +1 -1
  20. package/dist/render.js +25 -9
  21. package/dist/render.js.map +1 -1
  22. package/dist/tools/bash.d.ts.map +1 -1
  23. package/dist/tools/bash.js +79 -32
  24. package/dist/tools/bash.js.map +1 -1
  25. package/dist/tools/find.d.ts.map +1 -1
  26. package/dist/tools/find.js +29 -10
  27. package/dist/tools/find.js.map +1 -1
  28. package/dist/tools/grep.d.ts.map +1 -1
  29. package/dist/tools/grep.js +20 -10
  30. package/dist/tools/grep.js.map +1 -1
  31. package/dist/tools/ls.d.ts.map +1 -1
  32. package/dist/tools/ls.js +11 -5
  33. package/dist/tools/ls.js.map +1 -1
  34. package/dist/tools/metrics.d.ts +6 -1
  35. package/dist/tools/metrics.d.ts.map +1 -1
  36. package/dist/tools/metrics.js +21 -8
  37. package/dist/tools/metrics.js.map +1 -1
  38. package/dist/tools/read.d.ts +2 -1
  39. package/dist/tools/read.d.ts.map +1 -1
  40. package/dist/tools/read.js +97 -31
  41. package/dist/tools/read.js.map +1 -1
  42. package/dist/types.d.ts +13 -4
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/working-indicator.d.ts +39 -31
  45. package/dist/working-indicator.d.ts.map +1 -1
  46. package/dist/working-indicator.js +114 -87
  47. package/dist/working-indicator.js.map +1 -1
  48. package/package.json +1 -1
  49. package/src/config.ts +51 -0
  50. package/src/fff.ts +158 -29
  51. package/src/index.ts +77 -24
  52. package/src/prompt-editor.ts +102 -0
  53. package/src/render.ts +30 -11
  54. package/src/tools/bash.ts +100 -34
  55. package/src/tools/find.ts +39 -17
  56. package/src/tools/grep.ts +26 -15
  57. package/src/tools/ls.ts +20 -10
  58. package/src/tools/metrics.ts +28 -8
  59. package/src/tools/read.ts +126 -31
  60. package/src/types.ts +14 -4
  61. package/src/working-indicator.ts +123 -95
@@ -1,19 +1,17 @@
1
1
  /**
2
- * omp-style shimmer working indicator (widget takeover).
2
+ * omp-style shimmer working indicator.
3
3
  *
4
4
  * Ports the shimmer sweep from oh-my-pi's `modes/theme/shimmer.ts`: the sweep
5
5
  * is discretized into one pre-rendered frame per band position, cycled at
6
6
  * 1000/30 ms so the band travels 30 cells/second — omp's exact speed.
7
7
  *
8
- * Why a widget: the host `Loader` extends `Text` with a hardcoded paddingX=1,
9
- * and `setWorkingIndicator({ frames })` cannot change host component layout.
10
- * To render the row flush-left we hide the host loader
11
- * (`setWorkingVisible(false)`) and install our own zero-padding component via
12
- * `setWidget(..., "aboveEditor")`, animating it with a 33ms interval.
8
+ * On hosts that expose Pi's embedded working-status API, frames are delegated
9
+ * to the host so the row is rendered in the editor's top border. Older hosts
10
+ * without that API use the zero-padding widget fallback above the editor.
13
11
  *
14
- * Frame layout: `<dim spinner> <shimmer text> <dim interrupt hint>`, one full
15
- * sweep per phrase — `texts` rotates through phrases chapter by chapter with
16
- * a continuous spinner phase.
12
+ * Frame layout: `<dim spinner> <shimmer text>`, one full sweep per phrase —
13
+ * `texts` rotates through phrases chapter by chapter with a continuous spinner
14
+ * phase. The extension adds no interrupt hint or token-count suffix.
17
15
  */
18
16
 
19
17
  import { truncateToWidth } from "@earendil-works/pi-tui";
@@ -61,6 +59,7 @@ export interface WorkingIndicatorSettings {
61
59
  mode: WorkingIndicatorMode;
62
60
  palette: WorkingIndicatorPalette;
63
61
  bold: boolean;
62
+ /** @deprecated Retained for config compatibility; no interrupt hint is rendered. */
64
63
  hint: boolean;
65
64
  /** Tint mid/high tiers (and dim the spinner) with a per-session accent color. */
66
65
  sessionAccent: boolean;
@@ -261,7 +260,7 @@ export interface WorkingFrameOptions {
261
260
  spinnerColor?: string;
262
261
  /** Wrap every frame in italic (pi's hidden-thinking label is italic). */
263
262
  italic?: boolean;
264
- /** Pre-colored suffix (e.g. the dim interrupt hint) appended to every frame. */
263
+ /** @deprecated Retained for call-site compatibility; suffixes are not rendered. */
265
264
  hint?: string;
266
265
  intervalMs?: number;
267
266
  }
@@ -277,7 +276,6 @@ export function buildWorkingFrames(
277
276
  ): { frames: string[]; intervalMs: number } {
278
277
  const intervalMs = options.intervalMs ?? WORKING_INTERVAL_MS;
279
278
  const bold = options.bold ?? true;
280
- const suffix = options.hint ?? "";
281
279
  const withSpinner = options.spinner ?? true;
282
280
  const spinnerColor = options.spinnerColor ?? options.ansi.low;
283
281
  const highOpen = bold ? `${BOLD_OPEN}${options.ansi.high}` : options.ansi.high;
@@ -331,7 +329,7 @@ export function buildWorkingFrames(
331
329
  text,
332
330
  ranges,
333
331
  Array.from({ length: total }, () => "mid" as Tier),
334
- )}${suffix}`,
332
+ )}`,
335
333
  );
336
334
  return { frames, intervalMs };
337
335
  }
@@ -344,7 +342,7 @@ export function buildWorkingFrames(
344
342
  text,
345
343
  ranges,
346
344
  Array.from({ length: total }, () => "high" as Tier),
347
- )}${suffix}`,
345
+ )}`,
348
346
  );
349
347
  continue;
350
348
  }
@@ -353,20 +351,20 @@ export function buildWorkingFrames(
353
351
  const goingRight = sweep < range;
354
352
  const head = goingRight ? sweep : cycleCells - sweep;
355
353
  const tiers = Array.from({ length: total }, (_, i) => tierFor(kittIntensity(i, head, goingRight)));
356
- frames.push(`${spinnerPrefix(spinnerIndex++)}${paintFrame(text, ranges, tiers)}${suffix}`);
354
+ frames.push(`${spinnerPrefix(spinnerIndex++)}${paintFrame(text, ranges, tiers)}`);
357
355
  }
358
356
  } else {
359
357
  const period = total + CLASSIC_PADDING * 2;
360
358
  for (let pos = 0; pos < period; pos++) {
361
359
  const tiers = Array.from({ length: total }, (_, i) => tierFor(classicIntensity(i, pos)));
362
- frames.push(`${spinnerPrefix(spinnerIndex++)}${paintFrame(text, ranges, tiers)}${suffix}`);
360
+ frames.push(`${spinnerPrefix(spinnerIndex++)}${paintFrame(text, ranges, tiers)}`);
363
361
  }
364
362
  }
365
363
  }
366
364
  return { frames, intervalMs };
367
365
  }
368
366
 
369
- /** Estimate streamed output tokens: provider `usage.output` when exposed, else visible chars ÷ 4. */
367
+ /** @deprecated The working row no longer displays token stats. */
370
368
  export function workingTokens(message: unknown): number {
371
369
  const usage = (message as { usage?: { output?: unknown } } | undefined)?.usage;
372
370
  const output = Number(usage?.output);
@@ -391,8 +389,8 @@ export interface WidgetTuiLike {
391
389
 
392
390
  /**
393
391
  * Zero-padding working-row component. The host renders whatever `render()`
394
- * returns with no margins, so the row sits flush-left — the thing the frames
395
- * API could not do.
392
+ * returns with no margins, so the legacy widget row sits flush-left without
393
+ * relying on the host loader's padding.
396
394
  */
397
395
  export class WorkingWidget {
398
396
  #frames: string[] = [];
@@ -402,7 +400,6 @@ export class WorkingWidget {
402
400
  #tui: WidgetTuiLike | undefined;
403
401
  #started = false;
404
402
  #disposed = false;
405
- #stats: string | undefined;
406
403
 
407
404
  setFrames(frames: string[], intervalMs: number): void {
408
405
  this.#frames = frames;
@@ -421,11 +418,8 @@ export class WorkingWidget {
421
418
  this.#tui?.requestRender();
422
419
  }
423
420
 
424
- /** Dynamic right-side status segment (e.g. live token count), styled dim at
425
- * render time so theme changes don't bake stale ANSI into it. */
426
- setStats(text: string | undefined): void {
427
- this.#stats = text || undefined;
428
- }
421
+ /** @deprecated Token stats are owned by Pi and are no longer rendered here. */
422
+ setStats(_text: string | undefined): void {}
429
423
 
430
424
  start(): void {
431
425
  if (this.#disposed || this.#started) return;
@@ -451,8 +445,7 @@ export class WorkingWidget {
451
445
  render(width: number): string[] {
452
446
  if (!this.#started || this.#frames.length === 0) return [];
453
447
  const frame = this.#frames[this.#index % this.#frames.length] ?? "";
454
- const suffix = this.#stats ? `${FG_DIM}${this.#stats}${RESET_FG}` : "";
455
- return [truncateToWidth(frame + suffix, Math.max(1, width))];
448
+ return [truncateToWidth(frame, Math.max(1, width))];
456
449
  }
457
450
 
458
451
  invalidate(): void {
@@ -500,7 +493,7 @@ export interface PerRowThinkingLabels {
500
493
  /** Mark the message currently streaming a thinking block (its row animates). */
501
494
  setActive(timestamp: number): void;
502
495
  /** Freeze a message's completed thinking duration in ms (its row shows
503
- * `Thought for Xs` derived from this). */
496
+ * `Thought for X` derived from this). */
504
497
  complete(timestamp: number, durationMs: number): void;
505
498
  /** Drop the active mark (run ended); completed rows keep their durations. */
506
499
  clearActive(): void;
@@ -518,7 +511,7 @@ interface RowInternals {
518
511
  * Intercept the host's per-row label fan-out method
519
512
  * (`AssistantMessageComponent.prototype.setHiddenThinkingLabel`): each row
520
513
  * substitutes the incoming global label with its own — the streaming row
521
- * animates, completed rows stay frozen at their own `Thought for Xs`, unknown
514
+ * animates, completed rows stay frozen at their own `Thought for X`, unknown
522
515
  * rows fall back to pi's default. Any per-call failure passes the incoming
523
516
  * label through unchanged, so a host internals change degrades to pi-pretty's
524
517
  * global-label behavior instead of breaking. Durations live per session.
@@ -546,7 +539,7 @@ export function installPerRowThinkingLabels(componentClass: unknown): PerRowThin
546
539
  if (typeof ts !== "number") return incoming;
547
540
  if (ts === state.activeTs) return incoming;
548
541
  const ms = state.completed.get(ts);
549
- return ms === undefined ? THINKING_LABEL : `Thought for ${formatThinkingDuration(ms)}`;
542
+ return ms === undefined ? THINKING_LABEL : `${THOUGHT_LABEL} ${formatThinkingDuration(ms)}`;
550
543
  } catch {
551
544
  return incoming;
552
545
  }
@@ -595,11 +588,15 @@ export interface ThinkingTimer {
595
588
  restore(): void;
596
589
  }
597
590
 
598
- const THINKING_LABEL = "Thinking...";
591
+ const THINKING_LABEL = " Thinking...";
592
+ const THOUGHT_LABEL = " Thought for";
593
+ const padThinkingLabel = (label: string): string => (label.startsWith(" ") ? label : ` ${label}`);
599
594
 
600
- /** Format elapsed milliseconds as whole seconds with compact padded units. */
595
+ /** Format sub-second elapsed time as milliseconds, then use compact whole seconds. */
601
596
  export function formatThinkingDuration(elapsedMs: number): string {
602
- const totalSeconds = Math.floor(elapsedMs / 1000);
597
+ const wholeMs = Math.max(0, Math.floor(elapsedMs));
598
+ if (wholeMs < 1000) return `${wholeMs}ms`;
599
+ const totalSeconds = Math.floor(wholeMs / 1000);
603
600
  const seconds = totalSeconds % 60;
604
601
  const totalMinutes = Math.floor(totalSeconds / 60);
605
602
  const minutes = totalMinutes % 60;
@@ -635,7 +632,7 @@ export function createThinkingTimer(
635
632
  if (terminal) return undefined;
636
633
  terminal = true;
637
634
  const ms = elapsed();
638
- animator.show(`Thought for ${formatThinkingDuration(ms)}`);
635
+ animator.show(`${THOUGHT_LABEL} ${formatThinkingDuration(ms)}`);
639
636
  return ms;
640
637
  },
641
638
  restore(): void {
@@ -662,8 +659,8 @@ export function thinkingBlockActive(message: unknown): boolean {
662
659
  * the working row. Pi renders the label as static italic `thinkingText` text;
663
660
  * the only extension lever is `setHiddenThinkingLabel(label)`, which rebuilds
664
661
  * chat children, so index.ts drives it at 30fps only while the streaming
665
- * message's last block is thinking. The elapsed label is rebuilt only when its
666
- * whole-second text changes; the shimmer frame still advances every tick.
662
+ * message's last block is thinking. The elapsed label is rebuilt when its
663
+ * displayed duration changes; the shimmer frame still advances every tick.
667
664
  *
668
665
  * Tiers: low = theme `thinkingText` (pi's own label look), mid/high = session
669
666
  * accent when enabled; every frame is italic like pi's label; no spinner.
@@ -692,14 +689,18 @@ export function createThinkingLabelAnimator(
692
689
  const accent = hexToAnsiFg(sessionAccentHex(sessionName));
693
690
  if (accent) ansi = { ...ansi, mid: accent, high: accent };
694
691
  }
695
- const buildLabelFrames = (label: string): string[] =>
696
- buildWorkingFrames([label], {
692
+ const buildLabelFrames = (label: string): string[] => {
693
+ const padded = padThinkingLabel(label);
694
+ const text = padded.trimStart();
695
+ const leftPadding = padded.slice(0, padded.length - text.length);
696
+ return buildWorkingFrames([text], {
697
697
  mode: workSettings.mode,
698
698
  ansi,
699
699
  bold: workSettings.bold,
700
700
  spinner: false,
701
701
  italic: true,
702
- }).frames;
702
+ }).frames.map((frame) => `${leftPadding}${frame}`);
703
+ };
703
704
 
704
705
  let currentLabel = THINKING_LABEL;
705
706
  let frames = buildLabelFrames(currentLabel);
@@ -711,9 +712,10 @@ export function createThinkingLabelAnimator(
711
712
  return frames;
712
713
  },
713
714
  tick(label = THINKING_LABEL): void {
714
- if (label !== currentLabel) {
715
- frames = buildLabelFrames(label);
716
- currentLabel = label;
715
+ const paddedLabel = padThinkingLabel(label);
716
+ if (paddedLabel !== currentLabel) {
717
+ frames = buildLabelFrames(paddedLabel);
718
+ currentLabel = paddedLabel;
717
719
  }
718
720
  // A static frame changes only when its elapsed-time label changes.
719
721
  if (appliedLabel === currentLabel && frames.length === 1) return;
@@ -722,7 +724,7 @@ export function createThinkingLabelAnimator(
722
724
  appliedLabel = currentLabel;
723
725
  },
724
726
  show(label: string): void {
725
- ui.setHiddenThinkingLabel(label);
727
+ ui.setHiddenThinkingLabel(padThinkingLabel(label));
726
728
  },
727
729
  restore(): void {
728
730
  // undefined → pi falls back to its default static label.
@@ -733,15 +735,24 @@ export function createThinkingLabelAnimator(
733
735
 
734
736
  // ─── Installation ────────────────────────────────────────────────────────────
735
737
 
736
- /** Structural slice of pi's Theme needed for the hint, palette, and thinking label. */
738
+ /** Structural slice of pi's Theme needed for the palette and thinking label. */
737
739
  export interface WorkingThemeLike {
738
- /** Narrowed to the only color the hint needs; pi's ThemeColor satisfies it. */
740
+ /** Retained for compatibility with older theme adapters. */
739
741
  fg?: (name: "dim", text: string) => string;
740
742
  getFgAnsi?: (name: "dim" | "muted" | "accent" | "thinkingText") => string;
741
743
  }
742
744
 
745
+ export interface WorkingStatusOptionsLike {
746
+ frames?: string[];
747
+ intervalMs?: number;
748
+ }
749
+
743
750
  export interface WorkingUiLike {
744
751
  theme?: WorkingThemeLike;
752
+ /** Hosts with Pi's embedded working-status API render this in the editor border. */
753
+ setWorkingIndicator?: (options?: WorkingStatusOptionsLike) => void;
754
+ /** Clear the host message appended after a custom native frame. */
755
+ setWorkingMessage?: (message?: string) => void;
745
756
  setWorkingVisible(visible: boolean): void;
746
757
  setWidget(
747
758
  key: string,
@@ -750,6 +761,7 @@ export interface WorkingUiLike {
750
761
  ): void;
751
762
  }
752
763
 
764
+ /** @deprecated Retained for compatibility with the former hint-resolution seam. */
753
765
  export interface KeybindingsLike {
754
766
  getKeys(name: string): string[];
755
767
  }
@@ -759,11 +771,11 @@ export interface WorkingIndicatorController {
759
771
  start(): void;
760
772
  /** Streaming ended — hide the row and pause the animation. */
761
773
  stop(): void;
762
- /** Remove the widget and stop the animation. */
774
+ /** Remove the indicator and stop the animation. */
763
775
  dispose(): void;
764
- /** Update the dim right-side status segment (live token count). */
776
+ /** @deprecated Token stats are owned by Pi and are no longer rendered here. */
765
777
  setStats(text: string | undefined): void;
766
- /** Request a host render via the widget's TUI handle. */
778
+ /** Request a host render for consumers that drive another animated label. */
767
779
  requestRender(): void;
768
780
  /** Pre-rendered frames (exposed for diagnostics). */
769
781
  readonly frames: readonly string[];
@@ -771,52 +783,14 @@ export interface WorkingIndicatorController {
771
783
 
772
784
  const WIDGET_KEY = "pi-pretty-working";
773
785
 
774
- async function loadHostKeybindings(): Promise<KeybindingsLike | undefined> {
775
- try {
776
- const tui = (await import("@earendil-works/pi-tui")) as typeof import("@earendil-works/pi-tui");
777
- return tui.getKeybindings?.();
778
- } catch {
779
- return undefined;
780
- }
781
- }
782
-
783
- function formatKeyText(keys: string[]): string {
784
- const darwin = process.platform === "darwin";
785
- return keys
786
- .join("/")
787
- .split("/")
788
- .map((key) =>
789
- key
790
- .split("+")
791
- .map((part) => (darwin && part.toLowerCase() === "alt" ? "option" : part))
792
- .join("+"),
793
- )
794
- .join("/");
795
- }
796
-
797
- async function resolveHint(
798
- ui: WorkingUiLike,
799
- deps?: { getKeybindings?: () => KeybindingsLike | undefined | Promise<KeybindingsLike | undefined> },
800
- ): Promise<string | undefined> {
801
- try {
802
- const loader = deps?.getKeybindings ?? loadHostKeybindings;
803
- const keybindings = await loader();
804
- const keys = keybindings?.getKeys?.("app.interrupt");
805
- if (!keys?.length) return undefined;
806
- const text = ` (${formatKeyText(keys)} to interrupt)`;
807
- return ui.theme?.fg ? ui.theme.fg("dim", text) : text;
808
- } catch {
809
- return undefined;
810
- }
811
- }
812
-
813
786
  /**
814
- * Take over pi's working row with our own flush-left shimmer widget.
787
+ * Install the custom working shimmer and return its lifecycle controller.
815
788
  *
816
- * Installs a zero-padding component above the editor and hides the host
817
- * loader; returns a controller the host lifecycle drives (`start` on
818
- * agent_start, `stop` on agent_end). No-op (host defaults untouched) when
819
- * disabled or the phrase list is empty.
789
+ * Current Pi hosts receive the pre-rendered frames through the embedded
790
+ * working-status API, which places them in the editor's top border. Older
791
+ * hosts use a zero-padding widget above the editor. The host lifecycle drives
792
+ * the controller (`start` on agent_start, `stop` on agent_end); disabled or
793
+ * empty phrase settings leave host defaults untouched.
820
794
  */
821
795
  export async function installWorkingIndicator(
822
796
  ui: WorkingUiLike,
@@ -835,6 +809,7 @@ export async function installWorkingIndicator(
835
809
  if (!settings.enabled) return noopController;
836
810
  const texts = settings.texts.map((t) => t.trim()).filter((t) => t.length > 0);
837
811
  if (texts.length === 0) return noopController;
812
+ void deps; // Retained for compatibility; interrupt hints are no longer resolved here.
838
813
 
839
814
  const ansiResolved = resolvePaletteAnsi(settings.palette, ui.theme);
840
815
  let ansi = ansiResolved;
@@ -847,20 +822,73 @@ export async function installWorkingIndicator(
847
822
  spinnerColor = hexToAnsiFg(dimAccentHex(accentHex));
848
823
  }
849
824
  }
850
- const hint = settings.hint ? await resolveHint(ui, deps) : undefined;
851
825
  const { frames, intervalMs } = buildWorkingFrames(texts, {
852
826
  mode: settings.mode,
853
827
  ansi,
854
828
  bold: settings.bold,
855
829
  spinnerColor,
856
- hint,
857
830
  });
858
831
  if (frames.length === 0) return noopController;
859
832
 
833
+ const setWorkingIndicator = ui.setWorkingIndicator;
834
+ const setWorkingMessage = ui.setWorkingMessage;
835
+ if (typeof setWorkingIndicator === "function" && typeof setWorkingMessage === "function") {
836
+ let started = false;
837
+ let disposed = false;
838
+ const updateNativeMessage = (): void => {
839
+ // Keep Pi's built-in message hidden because the custom frame already
840
+ // supplies the working label; no hint or token suffix is added here.
841
+ setWorkingMessage.call(ui, "");
842
+ };
843
+
844
+ try {
845
+ setWorkingIndicator.call(ui, { frames, intervalMs });
846
+ // Keep the native status row hidden until the extension lifecycle starts it.
847
+ // An empty message is intentional: all visible content lives in our frames.
848
+ updateNativeMessage();
849
+ ui.setWorkingVisible(false);
850
+ } catch (error: unknown) {
851
+ try {
852
+ setWorkingIndicator.call(ui);
853
+ setWorkingMessage.call(ui);
854
+ } catch {
855
+ // Best-effort rollback; the caller still restores host visibility.
856
+ }
857
+ throw error;
858
+ }
859
+ return {
860
+ start: () => {
861
+ if (disposed || started) return;
862
+ started = true;
863
+ updateNativeMessage();
864
+ ui.setWorkingVisible(true);
865
+ },
866
+ stop: () => {
867
+ if (!started) return;
868
+ started = false;
869
+ ui.setWorkingVisible(false);
870
+ },
871
+ dispose: () => {
872
+ if (disposed) return;
873
+ if (started) {
874
+ started = false;
875
+ ui.setWorkingVisible(false);
876
+ }
877
+ disposed = true;
878
+ setWorkingIndicator.call(ui);
879
+ setWorkingMessage.call(ui);
880
+ },
881
+ setStats: (_text: string | undefined) => {},
882
+ requestRender: () => updateNativeMessage(),
883
+ frames,
884
+ };
885
+ }
886
+
860
887
  const widget = new WorkingWidget();
861
888
  widget.setFrames(frames, intervalMs);
862
889
  // Widget first, visibility second: if setWidget throws (older host without
863
- // the API) the host loader was never hidden and pi's default remains.
890
+ // the native working-status API) the host loader was never hidden and pi's
891
+ // default remains.
864
892
  ui.setWidget(
865
893
  WIDGET_KEY,
866
894
  (tui) => {
@@ -877,7 +905,7 @@ export async function installWorkingIndicator(
877
905
  widget.dispose();
878
906
  ui.setWidget(WIDGET_KEY, undefined);
879
907
  },
880
- setStats: (text: string | undefined) => widget.setStats(text),
908
+ setStats: (_text: string | undefined) => {},
881
909
  requestRender: () => widget.requestRender(),
882
910
  frames,
883
911
  };