pi-cc-extensions 1.0.4 → 1.0.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.
package/README.en.md CHANGED
@@ -55,15 +55,16 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
55
55
  "enableAgentSummary": true, // per-turn tool summary
56
56
  "enableWorkingMessage": true, // Working... bottom token/elapsed
57
57
  "enableAliases": true, // /clear, /exit aliases
58
+ "enableEditorPrefix": true, // editor ❯ prefix
58
59
 
59
60
  // ui
60
61
  "expandedInputMaxLines": 5, // expanded tool Input lines; overflow shows a footer hint
61
62
  "expandedOutputMaxLines": 10, // expanded tool Output lines; overflow shows a footer hint
62
63
  "expandedPreviewMaxLines": 40, // max lines for expanded TaskList bodies (expanded diffs always show all)
63
- "expandedCardBackground": "userMessageBg", // expanded card background slot: userMessageBg (same as user prompts) / toolPendingBg (neutral) etc.
64
64
  "toolLabelClip": 0, // tool summary path/command clip length; 0 = fit width
65
65
  "showStartupHeader": true, // startup header (logo + tips) toggle
66
66
  "stickyUserMessage": true, // fullscreen: pin a user message's first line once it scrolls off the top edge, until the next one takes over
67
+ "scrollButtonMode": "dock", // fullscreen: extension dock row / native Pi floating overlay
67
68
 
68
69
  // diff
69
70
  "diffViewMode": "auto", // layout: auto / split / unified
@@ -77,7 +78,6 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
77
78
  "useSummaryTitlesAsThinkingTitle": true, // use latest summary as thinking title
78
79
  "previewLines": 3, // preview lines; 0 hides
79
80
  "animationIntervalMs": 90, // title animation interval (ms)
80
- "dimThinkingText": false, // dim thinking body text
81
81
 
82
82
  // footer
83
83
  "enableCustomFooter": true, // custom status bar
@@ -88,10 +88,10 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
88
88
  "footerCurrencyRate": null, // display units per 1 cost unit; null changes the symbol only
89
89
  "footerNerdIcons": true, // Nerd Font glyphs for git/cache; false = plain text
90
90
  "footerGitStatsMode": "working", // working (default): uncommitted; branch: cumulative net changes
91
- "footerHiddenKeys": [], // hidden plugin chip keys
92
- "footerLine1Keys": ["pi-usage"], // line1 plugin chip order; pi-usage is shown by default, data from @narumitw/pi-usage
93
- "footerLine2Keys": [], // line2 plugin chip order (after cwd/git)
94
- "footerLine3Keys": [], // line3 overflow slot; painted only when a chip is visible
91
+ "footerHiddenKeys": [], // hidden chip keys
92
+ "footerLine1Keys": ["model", "context", "cache", "rate", "cost", "pi-usage"], // chip order per line; builtins and plugins share one layout
93
+ "footerLine2Keys": ["cwd", "git"], // cwd: directory and session; git: branch and changes. Either can move to any line
94
+ "footerLine3Keys": [], // painted only when a chip is visible
95
95
  }
96
96
  ```
97
97
 
package/README.md CHANGED
@@ -55,15 +55,16 @@ pi install git:github.com/minuque/pi-cc-extensions
55
55
  "enableAgentSummary": true, // 每回合工具摘要
56
56
  "enableWorkingMessage": true, // Working... 底部 token/耗时
57
57
  "enableAliases": true, // /clear、/exit 别名
58
+ "enableEditorPrefix": true, // 输入框 ❯ 前缀
58
59
 
59
60
  // ui
60
61
  "expandedInputMaxLines": 5, // 展开工具卡 Input 可见行数,超出在末行显示展开提示
61
62
  "expandedOutputMaxLines": 10, // 展开工具卡 Output 可见行数,超出在末行显示展开提示
62
63
  "expandedPreviewMaxLines": 40, // 展开 TaskList 正文最大行数(展开 diff 始终全量)
63
- "expandedCardBackground": "userMessageBg", // 展开卡背景槽位;userMessageBg 与用户消息同色,toolPendingBg 为中性灰
64
64
  "toolLabelClip": 0, // 工具摘要 path/command 折叠字符数;0 = 按可用宽度
65
65
  "showStartupHeader": true, // 启动头(logo + tips)开关
66
66
  "stickyUserMessage": true, // fullscreen:userMessage 正文首行滚出视口上沿后钉住,直到下一条指令接手
67
+ "scrollButtonMode": "dock", // fullscreen:dock 独占行 / native Pi 原生浮层
67
68
 
68
69
  // diff
69
70
  "diffViewMode": "auto", // 布局:auto / split / unified
@@ -77,7 +78,6 @@ pi install git:github.com/minuque/pi-cc-extensions
77
78
  "useSummaryTitlesAsThinkingTitle": true, // 用最新摘要作思考标题
78
79
  "previewLines": 3, // 预览行数,0 隐藏
79
80
  "animationIntervalMs": 90, // 标题动画间隔(毫秒)
80
- "dimThinkingText": false, // thinking 正文用 dim 色
81
81
 
82
82
  // footer
83
83
  "enableCustomFooter": true, // 自定义状态栏
@@ -88,10 +88,10 @@ pi install git:github.com/minuque/pi-cc-extensions
88
88
  "footerCurrencyRate": null, // 1 单位费用换成多少显示币种;null 只换符号、不乘倍率
89
89
  "footerNerdIcons": true, // git/缓存用 Nerd Font 图标;false 为纯文本
90
90
  "footerGitStatsMode": "working", // working(默认):未提交改动;branch:分支累计净改动
91
- "footerHiddenKeys": [], // 隐藏的插件芯片 key
92
- "footerLine1Keys": ["pi-usage"], // line1 插件芯片顺序;pi-usage 默认显示,数据来自 @narumitw/pi-usage
93
- "footerLine2Keys": [], // line2 插件芯片顺序(接在 cwd/git 后)
94
- "footerLine3Keys": [] // line3 备用槽,有可见芯片才占行
91
+ "footerHiddenKeys": [], // 隐藏的芯片 key
92
+ "footerLine1Keys": ["model", "context", "cache", "rate", "cost", "pi-usage"], // 每行芯片顺序;内置项与插件同一套排布
93
+ "footerLine2Keys": ["cwd", "git"], // cwd:目录和会话名;git:分支和改动。可换到任意行
94
+ "footerLine3Keys": [] // 有可见芯片才占行
95
95
  }
96
96
  ```
97
97
 
@@ -1,4 +1,3 @@
1
- import type { ThemeBg } from "@earendil-works/pi-coding-agent";
2
1
  import type { CompactThinkingConfig } from "../feature/compact-thinking.ts";
3
2
  import {
4
3
  DEFAULT_FOOTER_CHIP_LAYOUT,
@@ -15,6 +14,7 @@ export type DiffViewMode = "auto" | "split" | "unified";
15
14
  export type DiffIndicatorMode = "bars" | "classic" | "none";
16
15
  export type FooterGitStatsMode = "working" | "branch";
17
16
  export type FooterTokenRateMode = "average" | "live";
17
+ export type ScrollButtonMode = "dock" | "native";
18
18
 
19
19
  export interface ToolDisplayConfig {
20
20
  diffViewMode: DiffViewMode;
@@ -57,23 +57,25 @@ export type Config = {
57
57
  writeDiffCollapsedLines: number;
58
58
  diffWordWrap: boolean;
59
59
  expandedPreviewMaxLines: number;
60
- expandedCardBackground: ThemeBg;
61
60
  expandedInputMaxLines: number;
62
61
  expandedOutputMaxLines: number;
63
62
  toolLabelClip: number;
64
63
  useSummaryTitlesAsThinkingTitle: boolean;
65
64
  previewLines: number;
66
65
  animationIntervalMs: number;
67
- dimThinkingText: boolean;
68
66
  showStartupHeader: boolean;
69
67
  /** fullscreen:userMessage 正文首行滚出视口上沿后钉住,直到下一条指令接手。 */
70
68
  stickyUserMessage: boolean;
69
+ /** fullscreen 回到底部按钮:扩展 dock 行,或 Pi 原生浮层。 */
70
+ scrollButtonMode: ScrollButtonMode;
71
71
  enableSessionReference: boolean;
72
72
  enableSubagentAutocomplete: boolean;
73
73
  enableContextCommand: boolean;
74
74
  enableAgentSummary: boolean;
75
75
  enableWorkingMessage: boolean;
76
76
  enableAliases: boolean;
77
+ /** 输入框 `❯ ` 前缀(占左侧 2 列,颜色随 thinking/bash mode 联动)。 */
78
+ enableEditorPrefix: boolean;
77
79
  enableCustomFooter: boolean;
78
80
  /** footer 输出速率芯片。关则不显示。 */
79
81
  footerTokenRate: boolean;
@@ -101,26 +103,13 @@ export const DIFF_VIEW_MODES: DiffViewMode[] = ["auto", "split", "unified"];
101
103
  export const DIFF_INDICATOR_MODES: DiffIndicatorMode[] = ["bars", "classic", "none"];
102
104
  export const FOOTER_GIT_STATS_MODES: FooterGitStatsMode[] = ["working", "branch"];
103
105
  export const FOOTER_TOKEN_RATE_MODES: FooterTokenRateMode[] = ["average", "live"];
106
+ export const SCROLL_BUTTON_MODES: ScrollButtonMode[] = ["dock", "native"];
104
107
  export const DIFF_SPLIT_MIN_WIDTH_VALUES = ["80", "100", "120", "140", "160", "180"];
105
108
  export const DIFF_COLLAPSED_LINES_VALUES = ["0", "12", "24", "36", "48", "80", "120"];
106
109
  /** Write collapsed presets. 0 = stats only (`+N -0` + expand hint). */
107
110
  export const WRITE_DIFF_COLLAPSED_LINES_VALUES = ["0", "4", "8", "12", "24", "36"];
108
111
  /** Presets for expanded body height — keep low options first so cycling stays TUI-friendly. */
109
112
  export const EXPANDED_PREVIEW_MAX_LINES_VALUES = ["40", "60", "80", "120", "200", "500", "2000"];
110
- /**
111
- * 展开卡可用的主题背景槽位。theme.bg/getBgAnsi 只认这些 token,
112
- * 别的名字会抛 Unknown theme color,所以按白名单校验;
113
- * 面板顺序把 userMessageBg(默认)和 toolPendingBg(内置主题的中性灰)放最前。
114
- */
115
- export const EXPANDED_CARD_BACKGROUND_SLOTS: readonly ThemeBg[] = [
116
- "userMessageBg",
117
- "toolPendingBg",
118
- "customMessageBg",
119
- "selectedBg",
120
- "searchMatchBg",
121
- "toolSuccessBg",
122
- "toolErrorBg",
123
- ];
124
113
  /** 展开工具卡 Input 可见行数预设。 */
125
114
  export const EXPANDED_INPUT_MAX_LINES_VALUES = ["5", "10", "20", "40", "80"];
126
115
  /** 展开工具卡 Output 可见行数预设。 */
@@ -153,23 +142,22 @@ export const DEFAULT_CONFIG: Config = {
153
142
  writeDiffCollapsedLines: DEFAULT_TOOL_DISPLAY_CONFIG.writeDiffCollapsedLines,
154
143
  diffWordWrap: DEFAULT_TOOL_DISPLAY_CONFIG.diffWordWrap,
155
144
  expandedPreviewMaxLines: DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines,
156
- /** 展开卡背景。userMessageBg 与用户消息同色(ccstyle 原约定)。 */
157
- expandedCardBackground: "userMessageBg",
158
145
  expandedInputMaxLines: 5,
159
146
  expandedOutputMaxLines: 10,
160
147
  toolLabelClip: 0,
161
148
  useSummaryTitlesAsThinkingTitle: true,
162
149
  previewLines: 3,
163
150
  animationIntervalMs: 90,
164
- dimThinkingText: false,
165
151
  showStartupHeader: true,
166
152
  stickyUserMessage: true,
153
+ scrollButtonMode: "dock",
167
154
  enableSessionReference: true,
168
155
  enableSubagentAutocomplete: true,
169
156
  enableContextCommand: true,
170
157
  enableAgentSummary: true,
171
158
  enableWorkingMessage: true,
172
159
  enableAliases: true,
160
+ enableEditorPrefix: true,
173
161
  enableCustomFooter: true,
174
162
  footerTokenRate: true,
175
163
  footerTokenRateMode: "average",
@@ -263,11 +251,6 @@ export function normalizeConfig(input: unknown): Config {
263
251
  10,
264
252
  50_000,
265
253
  ),
266
- expandedCardBackground: pickEnum(
267
- source.expandedCardBackground,
268
- EXPANDED_CARD_BACKGROUND_SLOTS,
269
- DEFAULT_CONFIG.expandedCardBackground,
270
- ),
271
254
  expandedInputMaxLines: pickPositiveInt(
272
255
  source.expandedInputMaxLines,
273
256
  DEFAULT_CONFIG.expandedInputMaxLines,
@@ -294,15 +277,20 @@ export function normalizeConfig(input: unknown): Config {
294
277
  source.animationIntervalMs,
295
278
  DEFAULT_CONFIG.animationIntervalMs,
296
279
  ),
297
- dimThinkingText: source.dimThinkingText === true,
298
280
  showStartupHeader: source.showStartupHeader !== false,
299
281
  stickyUserMessage: source.stickyUserMessage !== false,
282
+ scrollButtonMode: pickEnum(
283
+ source.scrollButtonMode,
284
+ SCROLL_BUTTON_MODES,
285
+ DEFAULT_CONFIG.scrollButtonMode,
286
+ ),
300
287
  enableSessionReference: source.enableSessionReference !== false,
301
288
  enableSubagentAutocomplete: source.enableSubagentAutocomplete !== false,
302
289
  enableContextCommand: source.enableContextCommand !== false,
303
290
  enableAgentSummary: source.enableAgentSummary !== false,
304
291
  enableWorkingMessage: source.enableWorkingMessage !== false,
305
292
  enableAliases: source.enableAliases !== false,
293
+ enableEditorPrefix: (source.enableEditorPrefix ?? source.enableInputPrompt) !== false, // 兼容旧配置键
306
294
  enableCustomFooter: source.enableCustomFooter !== false,
307
295
  footerTokenRate: source.footerTokenRate !== false,
308
296
  footerTokenRateMode: pickEnum(
@@ -364,15 +352,16 @@ export function formatConfigStatus(source: Config = config): string {
364
352
  `thinkingTitle=${source.useSummaryTitlesAsThinkingTitle ? "summary" : "default"}`,
365
353
  `thinkingPreview=${source.previewLines}`,
366
354
  `thinkingAnimation=${source.animationIntervalMs}ms`,
367
- `thinkingDim=${source.dimThinkingText ? "on" : "off"}`,
368
355
  `startupHeader=${source.showStartupHeader ? "on" : "off"}`,
369
356
  `stickyUserMessage=${source.stickyUserMessage ? "on" : "off"}`,
357
+ `scrollButton=${source.scrollButtonMode}`,
370
358
  `sessionRef=${source.enableSessionReference ? "on" : "off"}`,
371
359
  `subagentAuto=${source.enableSubagentAutocomplete ? "on" : "off"}`,
372
360
  `context=${source.enableContextCommand ? "on" : "off"}`,
373
361
  `agentSummary=${source.enableAgentSummary ? "on" : "off"}`,
374
362
  `workingMsg=${source.enableWorkingMessage ? "on" : "off"}`,
375
363
  `aliases=${source.enableAliases ? "on" : "off"}`,
364
+ `editorPrefix=${source.enableEditorPrefix ? "on" : "off"}`,
376
365
  `footer=${source.enableCustomFooter ? "on" : "off"}`,
377
366
  `footerTokenRate=${source.footerTokenRate ? "on" : "off"}`,
378
367
  `footerTokenRateMode=${source.footerTokenRateMode}`,
@@ -4,7 +4,7 @@
4
4
  * 渲染副作用(applyStyleMode / refreshCurrentTranscript)由 renderer 经
5
5
  * CcstylePanelHooks 注入,避免 config → renderer 循环依赖。
6
6
  */
7
- import { getSettingsListTheme, type ThemeBg } from "@earendil-works/pi-coding-agent";
7
+ import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
8
8
  import { Input, SettingsList, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
9
9
  import type { CompactThinkingController } from "../feature/compact-thinking.ts";
10
10
  import {
@@ -28,6 +28,7 @@ import {
28
28
  toggleFooterKeyHidden,
29
29
  type FooterChipLayout,
30
30
  } from "../feature/shell/footer-layout.ts";
31
+ import { applyPromptEditor, clearPromptEditor } from "../feature/shell/prompt-editor.ts";
31
32
  import { applyStartupHeader, clearStartupHeader } from "../feature/shell/startup-header.ts";
32
33
  import type { ToolGroupingHooks } from "../renderer/tool/grouping.ts";
33
34
  import {
@@ -38,7 +39,6 @@ import {
38
39
  DIFF_SPLIT_MIN_WIDTH_VALUES,
39
40
  DIFF_VIEW_MODES,
40
41
  EXCLUDE_RENDERER_CANDIDATES,
41
- EXPANDED_CARD_BACKGROUND_SLOTS,
42
42
  EXPANDED_INPUT_MAX_LINES_VALUES,
43
43
  EXPANDED_OUTPUT_MAX_LINES_VALUES,
44
44
  EXPANDED_PREVIEW_MAX_LINES_VALUES,
@@ -49,6 +49,7 @@ import {
49
49
  pickPositiveInt,
50
50
  pickPositiveNumber,
51
51
  pickToolLabelClip,
52
+ SCROLL_BUTTON_MODES,
52
53
  THINKING_ANIMATION_INTERVAL_VALUES,
53
54
  THINKING_PREVIEW_LINES_VALUES,
54
55
  TOOL_LABEL_CLIP_VALUES,
@@ -60,6 +61,7 @@ import {
60
61
  type DiffViewMode,
61
62
  type FooterGitStatsMode,
62
63
  type FooterTokenRateMode,
64
+ type ScrollButtonMode,
63
65
  } from "./config.ts";
64
66
 
65
67
  /** renderer 注入的渲染副作用,面板自身不触碰渲染状态。 */
@@ -86,7 +88,7 @@ function excludeRenderersDescription(names: readonly string[]): string {
86
88
 
87
89
  function customFooterDescription(enabled: boolean): string {
88
90
  if (!enabled) return "Pi native footer restored. Chip layout below still applies when turned on.";
89
- return "Custom status bar with model, context, cache, cost, git, and plugin chips.";
91
+ return "Custom status bar. Model, context, cache, rate, cost, cwd, git, and plugin chips share one layout.";
90
92
  }
91
93
 
92
94
  function footerTokenRateDescription(enabled: boolean): string {
@@ -114,7 +116,7 @@ function footerGitStatsDescription(mode: FooterGitStatsMode): string {
114
116
  }
115
117
 
116
118
  function pluginChipsDescription(): string {
117
- return `Arrange plugin chips: ${formatFooterChipSummary(config)}. Enter to edit.`;
119
+ return `Arrange footer chips: ${formatFooterChipSummary(config)}. Enter to edit.`;
118
120
  }
119
121
 
120
122
  function footerLayoutFromConfig(): FooterChipLayout {
@@ -234,14 +236,6 @@ function diffIndicatorDescription(mode: DiffIndicatorMode): string {
234
236
  return "Vertical bar indicators on changed lines (default).";
235
237
  }
236
238
 
237
- function expandedCardBackgroundDescription(slot: ThemeBg): string {
238
- if (slot === "userMessageBg") {
239
- return "Expanded cards share the user message background. Pick another slot to tell them apart.";
240
- }
241
- if (slot === "toolPendingBg") return "Expanded cards use the theme's neutral tool background.";
242
- return `Expanded cards use the theme's ${slot} background.`;
243
- }
244
-
245
239
  /** 额外功能开关项:on/off 二值,描述随状态切换;切换后需重启生效。 */
246
240
  function featureToggleSetting(
247
241
  id: string,
@@ -565,13 +559,6 @@ export async function showCcstylePanel(
565
559
  submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
566
560
  buildNumberInputSubmenu(theme, expandedMaxSetting, closeSubmenu),
567
561
  };
568
- const expandedCardBgSetting = {
569
- id: "expandedCardBackground",
570
- label: "Expanded card bg",
571
- description: expandedCardBackgroundDescription(config.expandedCardBackground),
572
- currentValue: config.expandedCardBackground,
573
- values: [...EXPANDED_CARD_BACKGROUND_SLOTS],
574
- };
575
562
  const thinkingTitleSetting = {
576
563
  id: "useSummaryTitlesAsThinkingTitle",
577
564
  label: "Summary title",
@@ -597,15 +584,6 @@ export async function showCcstylePanel(
597
584
  submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
598
585
  buildNumberInputSubmenu(theme, thinkingAnimationSetting, closeSubmenu),
599
586
  };
600
- const thinkingDimSetting = {
601
- id: "dimThinkingText",
602
- label: "Dim thinking text",
603
- description: config.dimThinkingText
604
- ? "Thinking text uses the theme's dim color."
605
- : "Keep the default thinking text color.",
606
- currentValue: config.dimThinkingText ? "on" : "off",
607
- values: ["on", "off"],
608
- };
609
587
  const startupHeaderSetting = {
610
588
  id: "showStartupHeader",
611
589
  label: "Startup header",
@@ -624,6 +602,16 @@ export async function showCcstylePanel(
624
602
  currentValue: config.stickyUserMessage ? "on" : "off",
625
603
  values: ["on", "off"],
626
604
  };
605
+ const scrollButtonSetting = {
606
+ id: "scrollButtonMode",
607
+ label: "Scroll to bottom",
608
+ description:
609
+ config.scrollButtonMode === "dock"
610
+ ? "Fullscreen: show the extension's dedicated Back to bottom row."
611
+ : "Fullscreen: use Pi's native floating Jump to latest overlay.",
612
+ currentValue: config.scrollButtonMode,
613
+ values: [...SCROLL_BUTTON_MODES],
614
+ };
627
615
 
628
616
  // 额外功能开关:注册于扩展加载期,切换后需重启(/reload)生效。
629
617
  const sessionReferenceToggle = featureToggleSetting(
@@ -736,6 +724,15 @@ export async function showCcstylePanel(
736
724
  currentValue: config.footerNerdIcons ? "on" : "off",
737
725
  values: ["on", "off"],
738
726
  };
727
+ const editorPrefixSetting = {
728
+ id: "enableEditorPrefix",
729
+ label: "Editor prefix",
730
+ description: config.enableEditorPrefix
731
+ ? "Show ❯ before the input line; color follows thinking level / bash mode."
732
+ : "Plain input line, no ❯ glyph.",
733
+ currentValue: config.enableEditorPrefix ? "on" : "off",
734
+ values: ["on", "off"],
735
+ };
739
736
  const customFooterSetting = {
740
737
  id: "enableCustomFooter",
741
738
  label: "Status bar",
@@ -773,7 +770,7 @@ export async function showCcstylePanel(
773
770
  };
774
771
  const pluginChipsSetting = {
775
772
  id: "footerPluginChips",
776
- label: "Plugin chips",
773
+ label: "Footer chips",
777
774
  description: pluginChipsDescription(),
778
775
  currentValue: formatFooterChipSummary(config),
779
776
  submenu: (_current: string, closeSubmenu: (selected?: string) => void) => {
@@ -868,6 +865,18 @@ export async function showCcstylePanel(
868
865
  ctx.ui.notify(`Updated ${id}: ${value}`, "info");
869
866
  return;
870
867
  }
868
+ if (id === "enableEditorPrefix") {
869
+ const enabled = value === "on";
870
+ updateConfig({ enableEditorPrefix: enabled });
871
+ editorPrefixSetting.currentValue = enabled ? "on" : "off";
872
+ editorPrefixSetting.description = enabled
873
+ ? "Show ❯ before the input line; color follows thinking level / bash mode."
874
+ : "Plain input line, no ❯ glyph.";
875
+ if (enabled) applyPromptEditor(ctx);
876
+ else clearPromptEditor(ctx);
877
+ ctx.ui.notify(`Updated ${id}: ${value}`, "info");
878
+ return;
879
+ }
871
880
  if (id === "footerNerdIcons") {
872
881
  const enabled = value === "on";
873
882
  updateConfig({ footerNerdIcons: enabled });
@@ -978,12 +987,6 @@ export async function showCcstylePanel(
978
987
  });
979
988
  expandedMaxSetting.currentValue = String(config.expandedPreviewMaxLines);
980
989
  break;
981
- case "expandedCardBackground":
982
- updateConfig({ expandedCardBackground: value as ThemeBg });
983
- expandedCardBgSetting.description = expandedCardBackgroundDescription(
984
- config.expandedCardBackground,
985
- );
986
- break;
987
990
  case "useSummaryTitlesAsThinkingTitle":
988
991
  updateConfig({ useSummaryTitlesAsThinkingTitle: value === "on" });
989
992
  break;
@@ -997,12 +1000,6 @@ export async function showCcstylePanel(
997
1000
  });
998
1001
  thinkingAnimationSetting.currentValue = String(config.animationIntervalMs);
999
1002
  break;
1000
- case "dimThinkingText":
1001
- updateConfig({ dimThinkingText: value === "on" });
1002
- thinkingDimSetting.description = config.dimThinkingText
1003
- ? "Thinking text uses the theme's dim color."
1004
- : "Keep the default thinking text color.";
1005
- break;
1006
1003
  case "stickyUserMessage": {
1007
1004
  updateConfig({ stickyUserMessage: value === "on" });
1008
1005
  stickyUserMessageSetting.description = config.stickyUserMessage
@@ -1010,6 +1007,14 @@ export async function showCcstylePanel(
1010
1007
  : "User messages scroll normally.";
1011
1008
  break;
1012
1009
  }
1010
+ case "scrollButtonMode": {
1011
+ updateConfig({ scrollButtonMode: value as ScrollButtonMode });
1012
+ scrollButtonSetting.description =
1013
+ config.scrollButtonMode === "dock"
1014
+ ? "Fullscreen: show the extension's dedicated Back to bottom row."
1015
+ : "Fullscreen: use Pi's native floating Jump to latest overlay.";
1016
+ break;
1017
+ }
1013
1018
  case "showStartupHeader": {
1014
1019
  const enabled = value === "on";
1015
1020
  updateConfig({ showStartupHeader: enabled });
@@ -1053,10 +1058,11 @@ export async function showCcstylePanel(
1053
1058
  expandedInputSetting,
1054
1059
  expandedOutputSetting,
1055
1060
  expandedMaxSetting,
1056
- expandedCardBgSetting,
1057
1061
  toolLabelClipSetting,
1058
1062
  stickyUserMessageSetting,
1063
+ scrollButtonSetting,
1059
1064
  startupHeaderSetting,
1065
+ editorPrefixSetting,
1060
1066
  ],
1061
1067
  },
1062
1068
  {
@@ -1074,12 +1080,7 @@ export async function showCcstylePanel(
1074
1080
  {
1075
1081
  id: "thinking",
1076
1082
  label: "Thinking",
1077
- items: [
1078
- thinkingTitleSetting,
1079
- thinkingPreviewSetting,
1080
- thinkingAnimationSetting,
1081
- thinkingDimSetting,
1082
- ],
1083
+ items: [thinkingTitleSetting, thinkingPreviewSetting, thinkingAnimationSetting],
1083
1084
  },
1084
1085
  {
1085
1086
  id: "footer",
@@ -18,8 +18,7 @@ import {
18
18
  wrapTextWithAnsi,
19
19
  } from "@earendil-works/pi-tui";
20
20
  import { Box, Markdown, Spacer, Text, type Component } from "@earendil-works/pi-tui";
21
- // 展开卡背景是 ccstyle 的渲染约定,不随上游 CompactThinkingConfig 走,直接读全局配置。
22
- import { config as styleConfig } from "../config/config.ts";
21
+ import { TOOL_CARD_BG_SLOT } from "../renderer/tool/tool-state.ts";
23
22
  import { isToolTuiFullscreen } from "../renderer/tool/names.ts";
24
23
  import {
25
24
  animateCompactThinkingText,
@@ -435,7 +434,7 @@ export class ThinkingPreviewBlock implements Component {
435
434
  : heading;
436
435
  const bgFn =
437
436
  this.theme && typeof this.theme.bg === "function"
438
- ? (text: string) => this.theme!.bg(styleConfig.expandedCardBackground, text)
437
+ ? (text: string) => this.theme!.bg(TOOL_CARD_BG_SLOT.success, text)
439
438
  : undefined;
440
439
  const box = new Box(1, 1, bgFn);
441
440
  box.addChild({
@@ -11,6 +11,7 @@ import {
11
11
  import { Key, Markdown, matchesKey, visibleWidth } from "@earendil-works/pi-tui";
12
12
  import { type Color, rgbColor } from "@earendil-works/pi-tui";
13
13
  import { padLine } from "../utils/format.ts";
14
+ import { mouseBaseButton, parseSgrMousePacket } from "../utils/sgr-mouse.ts";
14
15
 
15
16
  export type ContextPart = {
16
17
  label: string;
@@ -34,6 +35,61 @@ type ContextPreview = {
34
35
  content: string;
35
36
  };
36
37
 
38
+ /** Pi 居中 overlay 的屏幕起点。没有 renderedOverlayLayouts 时用它,避免把 1-based 屏幕坐标当局部坐标。 */
39
+ function centeredOverlayOrigin(
40
+ tui: any,
41
+ margin: number,
42
+ maxHeightPercent: number,
43
+ width: number,
44
+ height: number,
45
+ ) {
46
+ const termWidth = Math.max(1, tui.terminal?.columns ?? 80);
47
+ const termHeight = Math.max(1, tui.terminal?.rows ?? 24);
48
+ const availWidth = Math.max(1, termWidth - margin * 2);
49
+ const availHeight = Math.max(1, termHeight - margin * 2);
50
+ const maxHeight = Math.max(
51
+ 1,
52
+ Math.min(Math.floor((termHeight * maxHeightPercent) / 100), availHeight),
53
+ );
54
+ const effectiveHeight = Math.min(height, maxHeight);
55
+ return {
56
+ col: Math.max(
57
+ margin,
58
+ Math.min(margin + Math.floor((availWidth - width) / 2), termWidth - margin - width),
59
+ ),
60
+ row: Math.max(
61
+ margin,
62
+ Math.min(
63
+ margin + Math.floor((availHeight - effectiveHeight) / 2),
64
+ termHeight - margin - effectiveHeight,
65
+ ),
66
+ ),
67
+ };
68
+ }
69
+
70
+ function localMouseFromSgr(
71
+ tui: any,
72
+ mouse: { code: number; col: number; row: number; final: "M" | "m" },
73
+ fallback: { col: number; row: number },
74
+ ) {
75
+ // 用焦点组件自己的布局。取最上层会在多个 overlay 叠放时把下层的点击算偏。
76
+ const layouts = Array.isArray(tui.renderedOverlayLayouts) ? tui.renderedOverlayLayouts : [];
77
+ const focused = tui.focusedComponent;
78
+ const layout =
79
+ layouts.findLast((candidate: { entry?: { component?: unknown } }) =>
80
+ focused ? candidate.entry?.component === focused : false,
81
+ ) ??
82
+ (focused ? undefined : layouts.at(-1)) ??
83
+ fallback;
84
+ return {
85
+ col: mouse.col - 1 - layout.col,
86
+ row: mouse.row - 1 - layout.row,
87
+ release: mouse.final === "m",
88
+ motion: (mouse.code & 32) !== 0,
89
+ baseButton: mouseBaseButton(mouse.code),
90
+ };
91
+ }
92
+
37
93
  function normalizePreviewText(text: string): string {
38
94
  return text
39
95
  .replace(/\r\n?/g, "\n")
@@ -122,6 +178,7 @@ export async function showTextPreview(
122
178
  }
123
179
  | undefined;
124
180
  let scrollbarDragOffset: number | null = null;
181
+ let screenOrigin = { col: 0, row: 0 };
125
182
  const markdownView = new Markdown(content, 0, 0, getMarkdownTheme());
126
183
 
127
184
  const scrollTo = (nextOffset: number): void => {
@@ -277,6 +334,11 @@ export async function showTextPreview(
277
334
  else if (matchesKey(data, "pageDown")) scrollTo(scrollOffset + pageSize);
278
335
  else if (matchesKey(data, Key.home)) scrollTo(0);
279
336
  else if (matchesKey(data, Key.end)) scrollTo(totalLines - pageSize);
337
+ else {
338
+ const mouse = parseSgrMousePacket(data);
339
+ if (!mouse) return;
340
+ handlePreviewMouse(localMouseFromSgr(tui, mouse, screenOrigin));
341
+ }
280
342
  },
281
343
  // overlay 事件对象路径:官方 dispatchMouseToOverlay 把鼠标派到
282
344
  // handleMouse,event.x/y 是相对弹框左上角的 0-based 局部坐标,
@@ -362,8 +424,7 @@ export async function showTextPreview(
362
424
  const start = totalLines === 0 ? 0 : scrollOffset + 1;
363
425
  const end = Math.min(totalLines, scrollOffset + pageSize);
364
426
  const status = `${start}-${end} / ${totalLines} lines · ↑↓ PgUp/PgDn Home/End`;
365
-
366
- return [
427
+ const lines = [
367
428
  border(`╭${"─".repeat(inner)}╮`),
368
429
  `${border("│")}${padLine(` ${theme.bold(theme.fg("accent", title))}`, inner - actionsWidth)}${theme.fg(copyHovered ? "text" : "muted", COPY_LABEL)} ${theme.fg(escHovered ? "text" : "muted", ESC_LABEL)}${border("│")}`,
369
430
  `${border("├")}${border("─".repeat(inner))}${border("┤")}`,
@@ -372,6 +433,8 @@ export async function showTextPreview(
372
433
  `${border("│")}${padLine(theme.fg("dim", ` ${status}`), inner)}${border("│")}`,
373
434
  border(`╰${"─".repeat(inner)}╯`),
374
435
  ];
436
+ screenOrigin = centeredOverlayOrigin(tui, 2, 80, width, lines.length);
437
+ return lines;
375
438
  },
376
439
  };
377
440
  },
@@ -708,6 +771,7 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
708
771
  let escHitbox: { row: number; startCol: number; endCol: number } | undefined;
709
772
  let escHovered = false;
710
773
  let hoveredKey: PreviewKey | undefined;
774
+ let screenOrigin = { col: 0, row: 0 };
711
775
 
712
776
  /** context 主弹框鼠标共用处:handleInput(raw SGR)与 handleMouse(overlay
713
777
  * 事件对象)同一套 hover/选中/关闭行为。返回 true 表示被预览消费。 */
@@ -784,6 +848,9 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
784
848
  done(visiblePreviews[selectedPreviewIndex]?.key);
785
849
  return;
786
850
  }
851
+ const mouse = parseSgrMousePacket(data);
852
+ if (!mouse) return;
853
+ handleDialogMouse(localMouseFromSgr(tui, mouse, screenOrigin));
787
854
  },
788
855
  // overlay 事件对象路径:event.x/y 是弹框局部 0-based 坐标,
789
856
  // 与 hitbox 同基准。click 是 press+release 的合成,动作已在
@@ -874,6 +941,7 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
874
941
  ]
875
942
  : [];
876
943
  });
944
+ screenOrigin = centeredOverlayOrigin(tui, 1, 90, width, visibleHeight);
877
945
 
878
946
  return lines;
879
947
  },
@@ -417,9 +417,8 @@ export default function sessionReferenceExtension(pi: ExtensionAPI): void {
417
417
 
418
418
  getAvailableReferences = getReferences;
419
419
  if (ctx.mode === "tui") {
420
- // 预取是 detached 的,必须兜住 rejection,否则 stale 错误会变成
421
- // unhandled rejection 直接终止 Pi。
422
- void getReferences().catch(() => {});
420
+ // 不在这里预取:SessionManager.listAll() 会读完整个会话库,大库要几秒,
421
+ // 而启动并不需要结果。首次 @ 输入由 getSuggestions 触发加载。
423
422
  // Register after other session_start handlers. pi-fff claims every @
424
423
  // prefix, so a provider installed before it would never see session mentions.
425
424
  setTimeout(() => {