@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
@@ -141,14 +141,14 @@ function linkifyUrls(markdown: string): string {
141
141
  // Nerd Font 补丁字形(U+2460-U+2473)ink 超界,DirectWrite 下渲染会压住相邻字符;
142
142
  // 转成 ASCII "(n)" 后任何字体下宽度一致。U+2460-U+2473 连续,差值即序号。
143
143
  const CIRCLED_RE = /[①-⑳]/g;
144
+
144
145
  const BLOCKQUOTE_PREFIX = /^ {0,3}>[ \t]?/;
145
146
  const CODE_FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;
146
147
  const CODE_FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*$/;
147
148
 
148
- type FenceLine = { blockquoteDepth: number; content: string };
149
149
  type MarkdownFence = { marker: string; blockquoteDepth: number };
150
150
 
151
- function parseFenceLine(line: string): FenceLine {
151
+ function parseFenceLine(line: string): { blockquoteDepth: number; content: string } {
152
152
  let content = line;
153
153
  let blockquoteDepth = 0;
154
154
  while (BLOCKQUOTE_PREFIX.test(content)) {
@@ -178,83 +178,17 @@ function scanMarkdownFence(
178
178
  };
179
179
  }
180
180
 
181
- function replaceCircled(text: string): string {
182
- return text.replace(CIRCLED_RE, (ch) => `(${ch.codePointAt(0)! - 0x2460 + 1})`);
183
- }
184
-
185
- function backtickRunAt(
186
- text: string,
187
- start: number,
188
- skipEscaped: boolean,
189
- ): { start: number; end: number } | null {
190
- let index = text.indexOf("`", start);
191
- while (index >= 0) {
192
- let backslashes = 0;
193
- for (let cursor = index - 1; cursor >= 0 && text[cursor] === "\\"; cursor--) backslashes++;
194
- if (!skipEscaped || backslashes % 2 === 0) {
195
- let end = index + 1;
196
- while (text[end] === "`") end++;
197
- return { start: index, end };
198
- }
199
- index = text.indexOf("`", index + 1);
200
- }
201
- return null;
202
- }
203
-
204
- /** 转换连续 prose(可跨行),但保留有效 inline code spans。 */
205
- function deCircledProse(prose: string): string {
206
- let cursor = 0;
207
- let transformed = "";
208
- while (cursor < prose.length) {
209
- const opening = backtickRunAt(prose, cursor, true);
210
- if (!opening) {
211
- transformed += replaceCircled(prose.slice(cursor));
212
- break;
213
- }
214
- let closing = backtickRunAt(prose, opening.end, false);
215
- while (closing && closing.end - closing.start !== opening.end - opening.start) {
216
- closing = backtickRunAt(prose, closing.end, false);
217
- }
218
- if (!closing) {
219
- transformed += replaceCircled(prose.slice(cursor, opening.end));
220
- cursor = opening.end;
221
- continue;
222
- }
223
- transformed += replaceCircled(prose.slice(cursor, opening.start));
224
- transformed += prose.slice(opening.start, closing.end);
225
- cursor = closing.end;
226
- }
227
- return transformed;
228
- }
229
-
230
- /** 转换普通 Markdown 文本,但保留匹配 container 的 fenced blocks 与 inline code spans。 */
231
181
  function deCircled(markdown: string): string {
232
- const output: string[] = [];
233
- let prose: string[] = [];
234
182
  let fence: MarkdownFence | undefined;
235
- const flushProse = () => {
236
- if (prose.length === 0) return;
237
- output.push(deCircledProse(prose.join("\n")));
238
- prose = [];
239
- };
240
-
241
- for (const line of markdown.split("\n")) {
242
- const scanned = scanMarkdownFence(line, fence);
243
- fence = scanned.fence;
244
- if (scanned.protected) {
245
- flushProse();
246
- output.push(line);
247
- continue;
248
- }
249
- if (parseFenceLine(line).content.trim() === "") {
250
- flushProse();
251
- output.push(line);
252
- continue;
253
- }
254
- prose.push(line);
255
- }
256
- flushProse();
257
- return output.join("\n");
183
+ return markdown
184
+ .split("\n")
185
+ .map((line) => {
186
+ const scanned = scanMarkdownFence(line, fence);
187
+ fence = scanned.fence;
188
+ if (scanned.protected) return line;
189
+ return line.replace(CIRCLED_RE, (ch) => `(${ch.codePointAt(0)! - 0x2460 + 1})`);
190
+ })
191
+ .join("\n");
258
192
  }
259
193
 
260
194
  // ============================================================================
@@ -280,13 +214,12 @@ function normalizeMultilineLinks(markdown: string): string {
280
214
  };
281
215
  for (const line of markdown.split("\n")) {
282
216
  const scanned = scanMarkdownFence(line, fence);
217
+ const opening = !fence && scanned.fence;
218
+ const closing = fence && !scanned.fence;
283
219
  fence = scanned.fence;
284
- if (scanned.protected) {
285
- flush();
286
- out.push(line);
287
- } else {
288
- prose.push(line);
289
- }
220
+ if (opening || closing) flush();
221
+ if (scanned.protected) out.push(line);
222
+ else prose.push(line);
290
223
  }
291
224
  flush();
292
225
  return out.join("\n");
@@ -6,7 +6,7 @@ import {
6
6
  invalidateIoView,
7
7
  type ExpandedToolIoView,
8
8
  type ToolIoSection,
9
- } from "../tool/result.ts";
9
+ } from "../tool/tool-io.ts";
10
10
  import type { ToolGroupComponent } from "../tool/grouping.ts";
11
11
  import { componentAtLocalRow, type ComponentRowHit } from "./layout.ts";
12
12
  import { setScrollButtonHovered } from "./scroll.ts";
@@ -13,9 +13,8 @@ import {
13
13
  setHoveredCompactAssistant,
14
14
  } from "../compact-mode.ts";
15
15
  import { isMessageDisplayComponent } from "../tool/message-display.ts";
16
- import { config } from "../../config/config.ts";
17
16
  import { isLazyProxyTui } from "../../utils/fullscreen-detect.ts";
18
- import { setToolTuiFullscreen, isCollapseHintLine } from "../tool/show-more-hint.ts";
17
+ import { setToolTuiFullscreen, isCollapseHintLine } from "../tool/names.ts";
19
18
  import {
20
19
  type ExpandedToolIoView,
21
20
  getActiveIoViewFrame,
@@ -23,7 +22,7 @@ import {
23
22
  type IoViewFrameState,
24
23
  setActiveIoViewFrame,
25
24
  type ToolIoSection,
26
- } from "../tool/result.ts";
25
+ } from "../tool/tool-io.ts";
27
26
  import {
28
27
  collectToolComponents,
29
28
  extractToolFramePlacements,
@@ -76,6 +75,11 @@ import {
76
75
  setHoveredToolIo,
77
76
  type FullscreenHoverTarget,
78
77
  } from "./hover.ts";
78
+ import {
79
+ handleStickyPromptClick,
80
+ resetStickyPromptState,
81
+ updateStickyPromptHover,
82
+ } from "./sticky-prompt.ts";
79
83
 
80
84
  type FrameToolRender = {
81
85
  component: any;
@@ -308,10 +312,6 @@ function ensureFullscreenToolMouseMotion(tui: any): void {
308
312
  releaseFullscreenToolMouseMotion(tui);
309
313
  return;
310
314
  }
311
- // 面板改 scrollStepLines 后,下一帧渲染即同步(restore 仍按 original 恢复)。
312
- if (typeof tui.wheelScrollLines === "number" && tui.wheelScrollLines !== config.scrollStepLines) {
313
- tui.wheelScrollLines = config.scrollStepLines;
314
- }
315
315
  if (
316
316
  !toolMouseInteractionActive() ||
317
317
  tui.mouseEnabled === false ||
@@ -348,7 +348,6 @@ function releaseFullscreenToolMouseMotion(tui?: any): void {
348
348
  }
349
349
 
350
350
  const FULLSCREEN_VIEWPORT_PATCH = Symbol("ccstyle.fullscreen-viewport-patch");
351
- const FULLSCREEN_WHEEL_SCROLL_ORIGINAL = Symbol("ccstyle.fullscreen-wheel-scroll-original");
352
351
 
353
352
  /**
354
353
  * diff 结果组件自带 remainder 行:声明存在时只有那一行是展开入口(正文里的同名字样不算);
@@ -445,9 +444,15 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
445
444
  }
446
445
  // 展开 round 卡内工具时,让 compact 的强制折叠放行它(非 round 内工具为空操作)。
447
446
  if (isTool) markCompactRoundToolExpanded(component);
448
- // compact 摘要行自己记展开前的视口(收起时还原跟随),这里不动它。
449
- if (!isAssistant) keepViewportOnExpand(tui);
450
- component.setExpanded(true);
447
+ if (isAssistant) {
448
+ // compact 摘要行自己记展开前的视口(收起时还原跟随),所以它先展开再停跟随:
449
+ // 顺序反了快照会记下「未跟随」,收起时就交不回官方 follow。
450
+ component.setExpanded(true);
451
+ keepViewportOnExpand(tui);
452
+ } else {
453
+ keepViewportOnExpand(tui);
454
+ component.setExpanded(true);
455
+ }
451
456
  clearPendingCollapsePress();
452
457
  } else {
453
458
  // 普通工具截断头 show-more:打开全量预览(不收起)。
@@ -487,10 +492,6 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
487
492
  return true;
488
493
  }
489
494
 
490
- /**
491
- * fullscreen 鼠标悬停:collapsed 卡 [click to show more] hint、
492
- * expanded 卡截断头 show-more、回到底部按钮。motion 不 consume,官方链照常。
493
- */
494
495
  /** 点到的组件和外层卡是否属于同一张 compact 回合面板。 */
495
496
  function isSameExpandedRound(other: any, owner: any): boolean {
496
497
  if (!owner || !isCompactAssistantComponent(other)) return false;
@@ -501,6 +502,7 @@ function isSameExpandedRound(other: any, owner: any): boolean {
501
502
  /**
502
503
  * 原地展开时停在当前滚动位置:transcript 跟随底部时,内容变高会把视口拖到底,
503
504
  * 点击的行和它上方的外层面板被顶出屏幕。展开前关掉跟随,点击行留在原位。
505
+ * compact 摘要行同样适用:它的展开入口在 setExpanded 之后调用本函数(见调用点)。
504
506
  */
505
507
  function keepViewportOnExpand(tui: any): void {
506
508
  const view = tui.getPrimaryScrollView?.();
@@ -510,6 +512,10 @@ function keepViewportOnExpand(tui: any): void {
510
512
  if (view.isFollowingEnd === false) setScrollButtonVisible(true);
511
513
  }
512
514
 
515
+ /**
516
+ * fullscreen 鼠标悬停:collapsed 卡 [click to show more] hint、
517
+ * expanded 卡截断头 show-more、回到底部按钮。motion 不 consume,官方链照常。
518
+ */
513
519
  function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
514
520
  if (!isSgrIdleMotion(packet)) return;
515
521
  const layout = tui.currentLayout;
@@ -589,11 +595,6 @@ function patchFullscreenViewportInput(tui: any): void {
589
595
  const proto = Object.getPrototypeOf(tui);
590
596
  const original = proto?.handleViewportInput;
591
597
  if (typeof original !== "function") return;
592
- // 官方原生 routeWheel 已完整处理嵌套 ScrollView;只调整默认步进(config.scrollStepLines)。
593
- if (typeof tui.wheelScrollLines === "number") {
594
- tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL] = tui.wheelScrollLines;
595
- tui.wheelScrollLines = config.scrollStepLines;
596
- }
597
598
  tui[FULLSCREEN_VIEWPORT_PATCH] = true;
598
599
  tui.handleViewportInput = function (this: any, data: string) {
599
600
  if (toolMouseInteractionActive() && tui.mode === "fullscreen") {
@@ -622,12 +623,17 @@ function patchFullscreenViewportInput(tui: any): void {
622
623
  }
623
624
  } else if (!isSgrLeftPress(packet) && !isSgrIdleMotion(packet))
624
625
  clearPendingCollapsePressOnMove(packet);
626
+ if (isSgrLeftPress(packet) && handleStickyPromptClick(tui, packet)) {
627
+ return { consume: true };
628
+ }
625
629
  if (isSgrLeftPress(packet) && handleFullscreenToolClick(tui, packet)) {
626
630
  return { consume: true };
627
631
  }
628
632
  // 仅无按键移动走 hover;左键拖动(文本多选)放行官方选区,避免每像素命中+重绘。
629
633
  if (isSgrIdleMotion(packet)) {
630
- handleFullscreenToolHover(tui, packet);
634
+ // 粘性行命中时不进工具 hover:同一行只能有一个高亮。
635
+ if (updateStickyPromptHover(tui, packet)) applyFullscreenHover(tui, null);
636
+ else handleFullscreenToolHover(tui, packet);
631
637
  }
632
638
  }
633
639
  }
@@ -642,11 +648,6 @@ function restoreFullscreenViewportInput(tui: any): void {
642
648
  if (typeof proto?.handleViewportInput === "function") {
643
649
  tui.handleViewportInput = proto.handleViewportInput;
644
650
  }
645
- const originalWheelScrollLines = tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL];
646
- if (typeof originalWheelScrollLines === "number") {
647
- tui.wheelScrollLines = originalWheelScrollLines;
648
- tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL] = undefined;
649
- }
650
651
  tui[FULLSCREEN_VIEWPORT_PATCH] = false;
651
652
  }
652
653
 
@@ -1037,6 +1038,7 @@ export function resetToolHoverState(): void {
1037
1038
  setHoveredCompactAssistant(null);
1038
1039
  setScrollButtonVisible(false);
1039
1040
  setScrollButtonHovered(false);
1041
+ resetStickyPromptState();
1040
1042
  restoreOfficialScrollToEnd(getToolMouseTui());
1041
1043
  releaseFullscreenToolMouseMotion(getToolMouseTui());
1042
1044
  }
@@ -1,4 +1,4 @@
1
- import type { ExpandedToolIoView, ToolIoSection } from "../tool/result.ts";
1
+ import type { ExpandedToolIoView, ToolIoSection } from "../tool/tool-io.ts";
2
2
  import { walkComponentTree } from "../../utils/component-tree.ts";
3
3
  export {
4
4
  stripTerminalSequences,