@herbertgao/pi-extensions 2026.10.4 → 2026.10.6

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 (52) hide show
  1. package/README.md +2 -2
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +13 -9
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +13 -9
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +72 -16
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +262 -48
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +2 -3
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +1 -1
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +131 -28
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/index.ts +2 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/subagent.ts +3 -0
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer-currency.ts +29 -0
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +96 -59
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/git-stats.ts +162 -0
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/token-rate.ts +204 -0
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +51 -86
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +212 -74
  17. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +50 -362
  18. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +35 -3
  19. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +16 -83
  20. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +1 -1
  21. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +28 -26
  22. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +1 -1
  23. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/sticky-prompt.ts +450 -0
  24. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/ask-user-question.ts +173 -0
  25. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/ccstyle-tool.ts +467 -0
  26. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +5 -9
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/ansi-utils.ts +1 -24
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-edit-render.ts +64 -11
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-limits.ts +1 -1
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +41 -0
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +5 -1
  32. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-text.ts +2 -1
  33. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +36 -10
  34. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +1 -1
  35. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +140 -51
  36. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/message-display.ts +1 -1
  37. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +44 -4
  38. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +34 -803
  39. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/tool-io.ts +735 -0
  40. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/tool-state.ts +138 -0
  41. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/format.ts +9 -0
  42. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/patch-keys.ts +5 -0
  43. package/node_modules/@herbertgao/pi-cc-extensions/package.json +5 -4
  44. package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-dark.json +2 -2
  45. package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json +3 -3
  46. package/node_modules/@herbertgao/pi-subagents/package.json +2 -1
  47. package/node_modules/@herbertgao/sol-pi/README.md +3 -3
  48. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +4 -4
  49. package/node_modules/@herbertgao/sol-pi/agents-install.md +3 -3
  50. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +1 -1
  51. package/package.json +2 -2
  52. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/show-more-hint.ts +0 -48
@@ -11,7 +11,12 @@ import {
11
11
  applyCustomFooter,
12
12
  clearCustomFooter,
13
13
  getFooterStatusSnapshot,
14
+ refreshFooterGitStats,
14
15
  } from "../feature/shell/footer.ts";
16
+ import {
17
+ FOOTER_CURRENCY_PRESETS,
18
+ presetFooterCurrencyRate,
19
+ } from "../feature/shell/footer-currency.ts";
15
20
  import {
16
21
  footerChipDescription,
17
22
  footerLineOfKey,
@@ -37,21 +42,24 @@ import {
37
42
  EXPANDED_INPUT_MAX_LINES_VALUES,
38
43
  EXPANDED_OUTPUT_MAX_LINES_VALUES,
39
44
  EXPANDED_PREVIEW_MAX_LINES_VALUES,
45
+ FOOTER_GIT_STATS_MODES,
46
+ FOOTER_TOKEN_RATE_MODES,
40
47
  formatExcludeRenderers,
41
48
  getCompactThinkingConfig,
42
- pickInputClip,
43
49
  pickPositiveInt,
44
50
  pickPositiveNumber,
45
- SCROLL_STEP_LINES_VALUES,
51
+ pickToolLabelClip,
46
52
  THINKING_ANIMATION_INTERVAL_VALUES,
47
53
  THINKING_PREVIEW_LINES_VALUES,
48
- INPUT_CLIP_VALUES,
54
+ TOOL_LABEL_CLIP_VALUES,
49
55
  WRITE_DIFF_COLLAPSED_LINES_VALUES,
50
56
  updateConfig,
51
57
  type CompactStyleMode,
52
58
  type Config,
53
59
  type DiffIndicatorMode,
54
60
  type DiffViewMode,
61
+ type FooterGitStatsMode,
62
+ type FooterTokenRateMode,
55
63
  } from "./config.ts";
56
64
 
57
65
  /** renderer 注入的渲染副作用,面板自身不触碰渲染状态。 */
@@ -62,7 +70,7 @@ export type CcstylePanelHooks = {
62
70
 
63
71
  function modeSettingDescription(mode: CompactStyleMode): string {
64
72
  if (mode === "compact") {
65
- return "(Experimental) One summary line per assistant round; edit/write reuse the same Diff preview settings as on.";
73
+ return "One summary line per assistant round; edit/write reuse the same Diff preview settings as on.";
66
74
  }
67
75
  if (mode === "off") {
68
76
  return "Pi native tool rendering. Diff options below still apply independently.";
@@ -81,6 +89,30 @@ function customFooterDescription(enabled: boolean): string {
81
89
  return "Custom status bar with model, context, cache, cost, git, and plugin chips.";
82
90
  }
83
91
 
92
+ function footerTokenRateDescription(enabled: boolean): string {
93
+ return enabled
94
+ ? "Output rate chip after the cache hit rate. Turn off to hide it."
95
+ : "Output rate chip hidden.";
96
+ }
97
+
98
+ function footerTokenRateModeDescription(mode: FooterTokenRateMode): string {
99
+ return mode === "live"
100
+ ? "Sliding 1s rate while generating (live). Tool waits are excluded. Falls back to the run average between chunks and after the prompt."
101
+ : "Average over the whole user prompt (avg), including tool waits. Stays until the next prompt.";
102
+ }
103
+
104
+ function footerTokenRateTtftDescription(enabled: boolean): string {
105
+ return enabled
106
+ ? "Append time to first token, e.g. (TTFT: 812ms) or (TTFT: 1.2s). Turn off to show the rate only."
107
+ : "Time to first token hidden. The rate chip shows tok/s only.";
108
+ }
109
+
110
+ function footerGitStatsDescription(mode: FooterGitStatsMode): string {
111
+ return mode === "branch"
112
+ ? "Net changes since the merge base, including uncommitted edits. Base: origin/HEAD, then local main/master. Untracked files excluded; unavailable stats hidden."
113
+ : "Uncommitted net changes relative to HEAD (default). Includes staged and unstaged edits; excludes untracked files.";
114
+ }
115
+
84
116
  function pluginChipsDescription(): string {
85
117
  return `Arrange plugin chips: ${formatFooterChipSummary(config)}. Enter to edit.`;
86
118
  }
@@ -281,10 +313,22 @@ function buildExcludeRenderersSubmenu(
281
313
  };
282
314
  }
283
315
 
284
- /** 数值项手动输入子面板:预填当前值,Space 循环预设,输入数字自定义,Enter 应用,Esc 取消。 */
285
- function buildNumberInputSubmenu(
316
+ function footerCurrencyRateLabel(rate: number | null): string {
317
+ return rate === null ? "-" : String(rate);
318
+ }
319
+
320
+ /**
321
+ * 手动输入子面板。accept 返回字符串则提交,undefined 取消,null 用 invalid 提示。
322
+ */
323
+ function buildValueInputSubmenu(
286
324
  theme: any,
287
- setting: { label: string; values: readonly string[]; currentValue: string },
325
+ options: {
326
+ prompt: string;
327
+ initial: string;
328
+ hint: string;
329
+ accept: (raw: string) => string | null | undefined;
330
+ invalid?: (raw: string) => string;
331
+ },
288
332
  closeSubmenu: (selected?: string) => void,
289
333
  ): {
290
334
  render: (width: number) => string[];
@@ -293,27 +337,28 @@ function buildNumberInputSubmenu(
293
337
  } {
294
338
  const input = new Input();
295
339
  let error = "";
296
- input.setValue(setting.currentValue);
340
+ input.setValue(options.initial);
297
341
  input.onSubmit = (value: string) => {
298
342
  const raw = value.trim();
299
- if (raw === "") {
300
- closeSubmenu(); // 空输入 = 取消
343
+ const next = options.accept(raw);
344
+ if (next === undefined) {
345
+ closeSubmenu();
301
346
  return;
302
347
  }
303
- if (!Number.isFinite(Number(raw))) {
304
- error = `Invalid number: "${raw}"`;
348
+ if (next === null) {
349
+ error = options.invalid?.(raw) ?? "Invalid value";
305
350
  return;
306
351
  }
307
- closeSubmenu(raw);
352
+ closeSubmenu(next);
308
353
  };
309
354
  input.onEscape = () => closeSubmenu();
310
355
  return {
311
356
  render: (width: number) => {
312
357
  const safe = Math.max(0, Math.floor(width));
313
358
  const lines = [
314
- theme.fg("dim", ` ${setting.label} — custom value:`),
359
+ theme.fg("dim", options.prompt),
315
360
  ...input.render(safe),
316
- truncateToWidth(theme.fg("dim", " Enter to apply · Esc to go back"), safe),
361
+ truncateToWidth(theme.fg("dim", options.hint), safe),
317
362
  ];
318
363
  if (error !== "") lines.push(theme.fg("dim", ` ${error}`));
319
364
  return lines;
@@ -323,6 +368,25 @@ function buildNumberInputSubmenu(
323
368
  };
324
369
  }
325
370
 
371
+ /** 数值项:预填当前值,空输入取消,非数字留下错误。 */
372
+ function buildNumberInputSubmenu(
373
+ theme: any,
374
+ setting: { label: string; currentValue: string },
375
+ closeSubmenu: (selected?: string) => void,
376
+ ) {
377
+ return buildValueInputSubmenu(
378
+ theme,
379
+ {
380
+ prompt: ` ${setting.label} — custom value:`,
381
+ initial: setting.currentValue,
382
+ hint: " Enter to apply · Esc to go back",
383
+ accept: (raw) => (raw === "" ? undefined : Number.isFinite(Number(raw)) ? raw : null),
384
+ invalid: (raw) => `Invalid number: "${raw}"`,
385
+ },
386
+ closeSubmenu,
387
+ );
388
+ }
389
+
326
390
  /** Section tabs for /ccstyle — matches Zentui-style "A / B / C" headers. */
327
391
  type CcstyleSection = {
328
392
  id: "style" | "diff" | "thinking" | "ui" | "feature" | "footer";
@@ -378,8 +442,8 @@ export async function showCcstylePanel(
378
442
  id: "mode",
379
443
  label: "Mode",
380
444
  description: modeSettingDescription(config.mode),
381
- currentValue: config.mode === "compact" ? "compact (Experimental)" : config.mode,
382
- values: ["on", "compact (Experimental)", "off"],
445
+ currentValue: config.mode,
446
+ values: ["on", "compact", "off"],
383
447
  };
384
448
  // Tracks whether a nested submenu is open so Tab switches sections
385
449
  // only at the top level (mirrors Zentui settings: Tab = switch sections).
@@ -409,15 +473,15 @@ export async function showCcstylePanel(
409
473
  );
410
474
  },
411
475
  };
412
- const inputClipSetting = {
413
- id: "inputClip",
414
- label: "Input clip",
476
+ const toolLabelClipSetting = {
477
+ id: "toolLabelClip",
478
+ label: "Tool label clip",
415
479
  description:
416
480
  "Max characters for path/command/name in single and grouped tool summaries; 0 fits available width. Enter to type a custom value.",
417
- currentValue: String(config.inputClip),
418
- values: [...INPUT_CLIP_VALUES],
481
+ currentValue: String(config.toolLabelClip),
482
+ values: [...TOOL_LABEL_CLIP_VALUES],
419
483
  submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
420
- buildNumberInputSubmenu(theme, inputClipSetting, closeSubmenu),
484
+ buildNumberInputSubmenu(theme, toolLabelClipSetting, closeSubmenu),
421
485
  };
422
486
  const diffViewSetting = {
423
487
  id: "diffViewMode",
@@ -447,7 +511,7 @@ export async function showCcstylePanel(
447
511
  id: "editDiffCollapsedLines",
448
512
  label: "Edit collapsed lines",
449
513
  description:
450
- "How many edit/diff body lines to show before the expand hint (Ctrl+O / click). Write uses its own setting below.",
514
+ "Edit diff body lines before the expand hint (Ctrl+O / click). 0 shows only change counts and the hint.",
451
515
  currentValue: String(config.editDiffCollapsedLines),
452
516
  values: [...DIFF_COLLAPSED_LINES_VALUES],
453
517
  submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
@@ -551,14 +615,14 @@ export async function showCcstylePanel(
551
615
  currentValue: config.showStartupHeader ? "on" : "off",
552
616
  values: ["on", "off"],
553
617
  };
554
- const scrollStepSetting = {
555
- id: "scrollStepLines",
556
- label: "Scroll step",
557
- description: "Mouse wheel scroll lines in fullscreen mode.",
558
- currentValue: String(config.scrollStepLines),
559
- values: [...SCROLL_STEP_LINES_VALUES],
560
- submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
561
- buildNumberInputSubmenu(theme, scrollStepSetting, closeSubmenu),
618
+ const stickyUserMessageSetting = {
619
+ id: "stickyUserMessage",
620
+ label: "Sticky user message",
621
+ description: config.stickyUserMessage
622
+ ? "Fullscreen: pin a user message's first line once it scrolls off the top edge, until the next one takes over."
623
+ : "User messages scroll normally.",
624
+ currentValue: config.stickyUserMessage ? "on" : "off",
625
+ values: ["on", "off"],
562
626
  };
563
627
 
564
628
  // 额外功能开关:注册于扩展加载期,切换后需重启(/reload)生效。
@@ -604,6 +668,65 @@ export async function showCcstylePanel(
604
668
  "Aliases disabled.",
605
669
  config.enableAliases,
606
670
  );
671
+ const footerCurrencySetting = {
672
+ id: "footerCurrency",
673
+ label: "Cost currency",
674
+ description:
675
+ "ISO code for the status-bar cost. Switching currency fills its preset rate. USD clears the rate.",
676
+ currentValue: config.footerCurrency,
677
+ values: [...FOOTER_CURRENCY_PRESETS],
678
+ submenu: (_current: string, closeSubmenu: (selected?: string) => void) => {
679
+ nestedSubmenuOpen = true;
680
+ nestedHint = " Enter to apply · Esc back to Footer";
681
+ return buildValueInputSubmenu(
682
+ theme,
683
+ {
684
+ prompt: " Cost currency — ISO code:",
685
+ initial: footerCurrencySetting.currentValue,
686
+ hint: " Enter to apply · Esc to go back",
687
+ accept: (raw) => (raw === "" ? undefined : raw),
688
+ },
689
+ (selected) => {
690
+ nestedSubmenuOpen = false;
691
+ nestedHint = "";
692
+ closeSubmenu(selected);
693
+ },
694
+ );
695
+ },
696
+ };
697
+ const footerCurrencyRateSetting = {
698
+ id: "footerCurrencyRate",
699
+ label: "Cost rate",
700
+ description:
701
+ "Display units per 1 USD of cost. Switching currency replaces this. Empty keeps the number and only changes the symbol.",
702
+ currentValue: footerCurrencyRateLabel(config.footerCurrencyRate),
703
+ submenu: (_current: string, closeSubmenu: (selected?: string) => void) => {
704
+ nestedSubmenuOpen = true;
705
+ nestedHint = " Enter to apply · empty clears · Esc back to Footer";
706
+ return buildValueInputSubmenu(
707
+ theme,
708
+ {
709
+ prompt: " Cost rate — display units per 1 cost unit:",
710
+ initial:
711
+ footerCurrencyRateSetting.currentValue === "-"
712
+ ? ""
713
+ : footerCurrencyRateSetting.currentValue,
714
+ hint: " Enter to apply · empty clears conversion · Esc to go back",
715
+ accept: (raw) => {
716
+ if (raw === "" || raw === "-") return "-";
717
+ const n = Number(raw);
718
+ return Number.isFinite(n) && n > 0 ? raw : null;
719
+ },
720
+ invalid: () => "Rate must be a positive number",
721
+ },
722
+ (selected) => {
723
+ nestedSubmenuOpen = false;
724
+ nestedHint = "";
725
+ closeSubmenu(selected);
726
+ },
727
+ );
728
+ },
729
+ };
607
730
  const footerNerdIconsSetting = {
608
731
  id: "footerNerdIcons",
609
732
  label: "Nerd Font icons",
@@ -620,6 +743,34 @@ export async function showCcstylePanel(
620
743
  currentValue: config.enableCustomFooter ? "on" : "off",
621
744
  values: ["on", "off"],
622
745
  };
746
+ const footerGitStatsSetting = {
747
+ id: "footerGitStatsMode",
748
+ label: "Git changes",
749
+ description: footerGitStatsDescription(config.footerGitStatsMode),
750
+ currentValue: config.footerGitStatsMode,
751
+ values: FOOTER_GIT_STATS_MODES,
752
+ };
753
+ const footerTokenRateSetting = {
754
+ id: "footerTokenRate",
755
+ label: "Token rate",
756
+ description: footerTokenRateDescription(config.footerTokenRate),
757
+ currentValue: config.footerTokenRate ? "on" : "off",
758
+ values: ["on", "off"],
759
+ };
760
+ const footerTokenRateModeSetting = {
761
+ id: "footerTokenRateMode",
762
+ label: "Token rate mode",
763
+ description: footerTokenRateModeDescription(config.footerTokenRateMode),
764
+ currentValue: config.footerTokenRateMode,
765
+ values: FOOTER_TOKEN_RATE_MODES,
766
+ };
767
+ const footerTokenRateTtftSetting = {
768
+ id: "footerTokenRateTtft",
769
+ label: "Token rate TTFT",
770
+ description: footerTokenRateTtftDescription(config.footerTokenRateTtft),
771
+ currentValue: config.footerTokenRateTtft ? "on" : "off",
772
+ values: ["on", "off"],
773
+ };
623
774
  const pluginChipsSetting = {
624
775
  id: "footerPluginChips",
625
776
  label: "Plugin chips",
@@ -663,6 +814,60 @@ export async function showCcstylePanel(
663
814
  ctx.ui.notify(`Updated ${id}: ${value}`, "info");
664
815
  return;
665
816
  }
817
+ if (id === "footerCurrency") {
818
+ updateConfig({
819
+ footerCurrency: value,
820
+ footerCurrencyRate: presetFooterCurrencyRate(value),
821
+ });
822
+ footerCurrencySetting.currentValue = config.footerCurrency;
823
+ footerCurrencyRateSetting.currentValue = footerCurrencyRateLabel(config.footerCurrencyRate);
824
+ if (config.enableCustomFooter) applyCustomFooter(ctx);
825
+ ctx.ui.notify(
826
+ `Updated ${id}: ${config.footerCurrency} · ${footerCurrencyRateSetting.currentValue}`,
827
+ "info",
828
+ );
829
+ return;
830
+ }
831
+ if (id === "footerCurrencyRate") {
832
+ updateConfig({ footerCurrencyRate: value === "-" ? null : Number(value) });
833
+ footerCurrencyRateSetting.currentValue = footerCurrencyRateLabel(config.footerCurrencyRate);
834
+ if (config.enableCustomFooter) applyCustomFooter(ctx);
835
+ ctx.ui.notify(`Updated ${id}: ${footerCurrencyRateSetting.currentValue}`, "info");
836
+ return;
837
+ }
838
+ if (id === "footerGitStatsMode") {
839
+ updateConfig({ footerGitStatsMode: value as FooterGitStatsMode });
840
+ footerGitStatsSetting.currentValue = config.footerGitStatsMode;
841
+ footerGitStatsSetting.description = footerGitStatsDescription(config.footerGitStatsMode);
842
+ refreshFooterGitStats();
843
+ ctx.ui.notify(`Updated ${id}: ${value}`, "info");
844
+ return;
845
+ }
846
+ if (id === "footerTokenRate") {
847
+ const enabled = value === "on";
848
+ updateConfig({ footerTokenRate: enabled });
849
+ footerTokenRateSetting.currentValue = enabled ? "on" : "off";
850
+ footerTokenRateSetting.description = footerTokenRateDescription(enabled);
851
+ ctx.ui.notify(`Updated ${id}: ${value}`, "info");
852
+ return;
853
+ }
854
+ if (id === "footerTokenRateMode") {
855
+ updateConfig({ footerTokenRateMode: value as FooterTokenRateMode });
856
+ footerTokenRateModeSetting.currentValue = config.footerTokenRateMode;
857
+ footerTokenRateModeSetting.description = footerTokenRateModeDescription(
858
+ config.footerTokenRateMode,
859
+ );
860
+ ctx.ui.notify(`Updated ${id}: ${value}`, "info");
861
+ return;
862
+ }
863
+ if (id === "footerTokenRateTtft") {
864
+ const enabled = value === "on";
865
+ updateConfig({ footerTokenRateTtft: enabled });
866
+ footerTokenRateTtftSetting.currentValue = enabled ? "on" : "off";
867
+ footerTokenRateTtftSetting.description = footerTokenRateTtftDescription(enabled);
868
+ ctx.ui.notify(`Updated ${id}: ${value}`, "info");
869
+ return;
870
+ }
666
871
  if (id === "footerNerdIcons") {
667
872
  const enabled = value === "on";
668
873
  updateConfig({ footerNerdIcons: enabled });
@@ -682,16 +887,14 @@ export async function showCcstylePanel(
682
887
  return;
683
888
  }
684
889
  switch (id) {
685
- case "inputClip":
890
+ case "toolLabelClip":
686
891
  updateConfig({
687
- inputClip: pickInputClip(value),
892
+ toolLabelClip: pickToolLabelClip(value),
688
893
  });
689
- inputClipSetting.currentValue = String(config.inputClip);
894
+ toolLabelClipSetting.currentValue = String(config.toolLabelClip);
690
895
  break;
691
896
  case "mode": {
692
- // 选项值带 Experimental 标记,选择后还原为真实 mode 值。
693
- const mode: CompactStyleMode =
694
- value === "compact (Experimental)" ? "compact" : (value as CompactStyleMode);
897
+ const mode = value as CompactStyleMode;
695
898
  modeSetting.description = modeSettingDescription(mode);
696
899
  hooks.applyStyleMode(mode, ctx, toolGrouping);
697
900
  return;
@@ -719,7 +922,7 @@ export async function showCcstylePanel(
719
922
  editDiffCollapsedLines: pickPositiveInt(
720
923
  value,
721
924
  DEFAULT_CONFIG.editDiffCollapsedLines,
722
- 1,
925
+ 0,
723
926
  500,
724
927
  ),
725
928
  });
@@ -800,6 +1003,13 @@ export async function showCcstylePanel(
800
1003
  ? "Thinking text uses the theme's dim color."
801
1004
  : "Keep the default thinking text color.";
802
1005
  break;
1006
+ case "stickyUserMessage": {
1007
+ updateConfig({ stickyUserMessage: value === "on" });
1008
+ stickyUserMessageSetting.description = config.stickyUserMessage
1009
+ ? "Fullscreen: pin a user message's first line once it scrolls off the top edge, until the next one takes over."
1010
+ : "User messages scroll normally.";
1011
+ break;
1012
+ }
803
1013
  case "showStartupHeader": {
804
1014
  const enabled = value === "on";
805
1015
  updateConfig({ showStartupHeader: enabled });
@@ -810,12 +1020,6 @@ export async function showCcstylePanel(
810
1020
  else clearStartupHeader(ctx);
811
1021
  break;
812
1022
  }
813
- case "scrollStepLines":
814
- updateConfig({
815
- scrollStepLines: pickPositiveInt(value, DEFAULT_CONFIG.scrollStepLines, 1, 50),
816
- });
817
- scrollStepSetting.currentValue = String(config.scrollStepLines);
818
- break;
819
1023
  default:
820
1024
  return;
821
1025
  }
@@ -850,9 +1054,9 @@ export async function showCcstylePanel(
850
1054
  expandedOutputSetting,
851
1055
  expandedMaxSetting,
852
1056
  expandedCardBgSetting,
853
- inputClipSetting,
1057
+ toolLabelClipSetting,
1058
+ stickyUserMessageSetting,
854
1059
  startupHeaderSetting,
855
- scrollStepSetting,
856
1060
  ],
857
1061
  },
858
1062
  {
@@ -880,7 +1084,17 @@ export async function showCcstylePanel(
880
1084
  {
881
1085
  id: "footer",
882
1086
  label: "Footer",
883
- items: [customFooterSetting, footerNerdIconsSetting, pluginChipsSetting],
1087
+ items: [
1088
+ customFooterSetting,
1089
+ footerTokenRateSetting,
1090
+ footerTokenRateModeSetting,
1091
+ footerTokenRateTtftSetting,
1092
+ footerCurrencySetting,
1093
+ footerCurrencyRateSetting,
1094
+ footerNerdIconsSetting,
1095
+ footerGitStatsSetting,
1096
+ pluginChipsSetting,
1097
+ ],
884
1098
  },
885
1099
  ];
886
1100
 
@@ -39,9 +39,8 @@ function nonEmptyString(value: unknown): value is string {
39
39
  }
40
40
 
41
41
  function toolPath(args?: Record<string, unknown> | null): string | undefined {
42
- // path 为空字符串时回退 file_path 别名,避免该文件不被计入去重集合。
43
- if (nonEmptyString(args?.path)) return args.path;
44
- return nonEmptyString(args?.file_path) ? args.file_path : undefined;
42
+ const path = args?.path ?? args?.file_path;
43
+ return nonEmptyString(path) ? path : undefined;
45
44
  }
46
45
 
47
46
  /** 累积一次 agent 回合的工具统计;agent_start 时新建实例。 */
@@ -20,7 +20,7 @@ import {
20
20
  import { Box, Markdown, Spacer, Text, type Component } from "@earendil-works/pi-tui";
21
21
  // 展开卡背景是 ccstyle 的渲染约定,不随上游 CompactThinkingConfig 走,直接读全局配置。
22
22
  import { config as styleConfig } from "../config/config.ts";
23
- import { isToolTuiFullscreen } from "../renderer/tool/show-more-hint.ts";
23
+ import { isToolTuiFullscreen } from "../renderer/tool/names.ts";
24
24
  import {
25
25
  animateCompactThinkingText,
26
26
  formatThoughtDuration,