@herbertgao/pi-extensions 2026.8.1 → 2026.8.2

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 (162) hide show
  1. package/README.md +5 -2
  2. package/node_modules/@czottmann/pi-automode/README.md +16 -2
  3. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +35 -0
  4. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +18 -9
  5. package/node_modules/@czottmann/pi-automode/docs/defaults.md +6 -2
  6. package/node_modules/@czottmann/pi-automode/examples/automode.local.json +6 -0
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +11 -7
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +100 -0
  9. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +32 -0
  10. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +78 -3
  11. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +16 -4
  12. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +23 -1
  13. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +22 -1
  14. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +18 -2
  15. package/node_modules/@czottmann/pi-automode/package.json +4 -4
  16. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +5 -0
  17. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +5 -0
  18. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +5 -0
  19. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +5 -0
  20. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +5 -0
  21. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +5 -0
  22. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +23 -0
  23. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +99 -0
  24. package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +30 -0
  25. package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +16 -0
  26. package/node_modules/@dietrichgebert/ponytail/AGENTS.md +32 -0
  27. package/node_modules/@dietrichgebert/ponytail/LICENSE +21 -0
  28. package/node_modules/@dietrichgebert/ponytail/README.es.md +298 -0
  29. package/node_modules/@dietrichgebert/ponytail/README.ko.md +315 -0
  30. package/node_modules/@dietrichgebert/ponytail/README.md +351 -0
  31. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +21 -0
  32. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +62 -0
  33. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
  34. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +115 -0
  35. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +27 -0
  36. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +27 -0
  37. package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
  38. package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
  39. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
  40. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
  41. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
  42. package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +44 -0
  43. package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +21 -0
  44. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +96 -0
  45. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +169 -0
  46. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +98 -0
  47. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +130 -0
  48. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +85 -0
  49. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.ps1 +24 -0
  50. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.sh +18 -0
  51. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +77 -0
  52. package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +26 -0
  53. package/node_modules/@dietrichgebert/ponytail/package.json +47 -0
  54. package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +211 -0
  55. package/node_modules/@dietrichgebert/ponytail/pi-extension/package.json +8 -0
  56. package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +60 -0
  57. package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +120 -0
  58. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +41 -0
  59. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +44 -0
  60. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +50 -0
  61. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +71 -0
  62. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +57 -0
  63. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -3
  64. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -3
  65. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +10 -6
  66. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary.ts +6 -2
  67. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -83
  68. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +1 -1
  69. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/markdown-enhance.ts +119 -0
  70. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +73 -9
  71. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +2 -2
  72. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-hover.ts +130 -0
  73. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-interaction.ts +65 -542
  74. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-layout.ts +160 -0
  75. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-packets.ts +143 -0
  76. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-scroll.ts +184 -0
  77. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-diff/diff-renderer.ts +2 -2
  78. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-grouping.ts +9 -2
  79. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +91 -0
  80. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/agent-summary.ts +1 -1
  81. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  82. package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-dark.json +1 -0
  83. package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json +2 -2
  84. package/node_modules/@herbertgao/pi-stash/README.md +9 -9
  85. package/node_modules/@herbertgao/pi-stash/package.json +1 -1
  86. package/node_modules/@herbertgao/pi-stash/src/index.ts +2 -1
  87. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +14 -0
  88. package/node_modules/@herbertgao/pi-subagents/README.md +6 -2
  89. package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
  90. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +85 -11
  91. package/node_modules/@herbertgao/pi-subagents/src/index.ts +28 -4
  92. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +12 -0
  93. package/node_modules/@herbertgao/pi-subagents/src/types.ts +2 -0
  94. package/node_modules/@narumitw/pi-btw/LICENSE +21 -0
  95. package/node_modules/@narumitw/pi-btw/README.md +210 -0
  96. package/node_modules/@narumitw/pi-btw/package.json +53 -0
  97. package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +632 -0
  98. package/node_modules/@narumitw/pi-btw/src/btw.ts +996 -0
  99. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +339 -0
  100. package/node_modules/@narumitw/pi-btw/src/index.ts +1 -0
  101. package/node_modules/@narumitw/pi-btw/src/menu.ts +296 -0
  102. package/node_modules/@narumitw/pi-btw/src/settings.ts +237 -0
  103. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +238 -0
  104. package/node_modules/@narumitw/pi-btw/src/text.ts +10 -0
  105. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +620 -0
  106. package/node_modules/@pi-plugins/fast-mode/LICENSE +21 -0
  107. package/node_modules/@pi-plugins/fast-mode/README.md +92 -0
  108. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts +6 -0
  109. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -0
  110. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +2026 -0
  111. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -0
  112. package/node_modules/@pi-plugins/fast-mode/package.json +73 -0
  113. package/node_modules/pi-mcp-adapter/CHANGELOG.md +59 -0
  114. package/node_modules/pi-mcp-adapter/README.md +62 -13
  115. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +377 -0
  116. package/node_modules/pi-mcp-adapter/commands.ts +9 -7
  117. package/node_modules/pi-mcp-adapter/config.ts +110 -49
  118. package/node_modules/pi-mcp-adapter/direct-tools.ts +34 -19
  119. package/node_modules/pi-mcp-adapter/elicitation-handler.ts +37 -36
  120. package/node_modules/pi-mcp-adapter/errors.ts +21 -17
  121. package/node_modules/pi-mcp-adapter/host-html-template.ts +13 -7
  122. package/node_modules/pi-mcp-adapter/index.ts +37 -14
  123. package/node_modules/pi-mcp-adapter/init.ts +9 -7
  124. package/node_modules/pi-mcp-adapter/json-schema-validator.ts +2 -2
  125. package/node_modules/pi-mcp-adapter/lifecycle.ts +8 -8
  126. package/node_modules/pi-mcp-adapter/logger.ts +1 -1
  127. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +36 -22
  128. package/node_modules/pi-mcp-adapter/mcp-auth.ts +120 -2
  129. package/node_modules/pi-mcp-adapter/mcp-code.ts +13 -11
  130. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +19 -13
  131. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +15 -11
  132. package/node_modules/pi-mcp-adapter/mcp-panel.ts +34 -15
  133. package/node_modules/pi-mcp-adapter/mcp-probe.ts +131 -34
  134. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +2 -2
  135. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +7 -4
  136. package/node_modules/pi-mcp-adapter/mcp-trace.ts +59 -64
  137. package/node_modules/pi-mcp-adapter/metadata-cache.ts +34 -26
  138. package/node_modules/pi-mcp-adapter/npx-resolver.ts +6 -4
  139. package/node_modules/pi-mcp-adapter/oauth-handler.ts +1 -1
  140. package/node_modules/pi-mcp-adapter/onboarding-state.ts +19 -11
  141. package/node_modules/pi-mcp-adapter/package.json +13 -8
  142. package/node_modules/pi-mcp-adapter/panel-keys.ts +20 -4
  143. package/node_modules/pi-mcp-adapter/prompts.ts +2 -2
  144. package/node_modules/pi-mcp-adapter/proxy-modes.ts +48 -11
  145. package/node_modules/pi-mcp-adapter/sampling-handler.ts +22 -15
  146. package/node_modules/pi-mcp-adapter/search-ranking.ts +2 -1
  147. package/node_modules/pi-mcp-adapter/server-manager.ts +165 -113
  148. package/node_modules/pi-mcp-adapter/session-recovery.ts +11 -12
  149. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +3 -3
  150. package/node_modules/pi-mcp-adapter/state.ts +3 -1
  151. package/node_modules/pi-mcp-adapter/tool-approval.ts +67 -4
  152. package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -5
  153. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +14 -2
  154. package/node_modules/pi-mcp-adapter/ts-shape.ts +9 -2
  155. package/node_modules/pi-mcp-adapter/types.ts +109 -22
  156. package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +40 -0
  157. package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +28 -19
  158. package/node_modules/pi-mcp-adapter/ui-server.ts +90 -58
  159. package/node_modules/pi-mcp-adapter/ui-session.ts +16 -14
  160. package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +4 -4
  161. package/node_modules/pi-mcp-adapter/utils.ts +23 -9
  162. package/package.json +22 -7
@@ -38,7 +38,7 @@ Run `/reload` after installation.
38
38
  | Feature | Description | Entry point |
39
39
  | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
40
40
  | Claude Code-style output | Tool summaries, expand/collapse, rich edit/write diffs, and`on` / `compact` / `off` modes | `/ccstyle` |
41
- | Fullscreen mouse interaction | Tool card/group click-to-toggle, previews, hover highlight, and a back-to-bottom button (motion reporting auto-enabled under tmux/zellij/screen) | `/ccstyle` |
41
+ | Fullscreen mouse interaction | Tool card/group click-to-toggle, previews, hover highlight, and a back-to-bottom button | `TUIMODE=fullscreen` or `--tui-mode fullscreen` |
42
42
  | Settings panel | `Style / Diff / Thinking / Feature` tabs: startup header toggle and wheel step | `/ccstyle` |
43
43
  | Context inspection | Usage breakdown and previews for the system prompt, tools, skills, and messages | `/context` |
44
44
  | Session references | Search and inject effective context from previous Sessions or existing SubAgents | `@session:` |
@@ -79,8 +79,6 @@ Run `/reload` after changing extensions.
79
79
  ## Compatibility
80
80
 
81
81
  - Node.js `>=22.19.0`, Pi `^0.84.0` (loaded through `pi.extensions` and `pi.themes` in the root `package.json`)
82
- - Fixed-editor features have moved to the official pipeline. `/ccstyle` mouse interactions remain available in TUI fullscreen mode.
83
- - Use version `0.8.46` or earlier only if you need the removed fixed-editor layout.
84
82
 
85
83
  ## Recommended companions
86
84
 
@@ -38,7 +38,7 @@ pi install npm:@herbertgao/pi-cc-extensions
38
38
  | 功能 | 说明 | 入口 |
39
39
  | ---------------------- | ---------------------------------------------------------------------------------------------------------- | ------------- |
40
40
  | Claude Code 风格输出 | 工具摘要、折叠展开、rich edit/write diff,以及`on` / `compact` / `off` 三种模式 | `/ccstyle` |
41
- | Fullscreen 鼠标交互 | 工具卡/group 点击展开与收起、预览、hover 高亮、回到底部按钮(tmux/zellij/screen 下自动补开 motion 上报) | `/ccstyle` |
41
+ | Fullscreen 鼠标交互 | 工具卡/group 点击展开与收起、预览、hover 高亮、回到底部按钮 | `TUIMODE=fullscreen` `--tui-mode fullscreen` |
42
42
  | 配置面板 | `Style / Diff / Thinking / Feature` 四页签,含启动头开关与滚轮步进 | `/ccstyle` |
43
43
  | 上下文检查 | 查看上下文占用,并预览 System prompt、Tools、Skills 和消息内容 | `/context` |
44
44
  | Session 引用 | 搜索并注入历史 Session 或现有 SubAgent 的有效上下文 | `@session:` |
@@ -79,8 +79,6 @@ bun run typecheck
79
79
  ## 兼容性
80
80
 
81
81
  - Node.js `>=22.19.0`,Pi `^0.84.0`(通过根目录 `package.json` 的 `pi.extensions` 和 `pi.themes` 加载)
82
- - fixed-editor 相关功能已迁移至官方管线:TUI mode=fullscreen 模式下 `/ccstyle` 鼠标交互仍可用。
83
- - 需要已移除的 fixed-editor 布局时,请使用 `0.8.46` 及更早版本。
84
82
 
85
83
  ## 推荐搭配
86
84
 
@@ -38,7 +38,7 @@ export type CcstylePanelHooks = {
38
38
 
39
39
  function modeSettingDescription(mode: CompactStyleMode): string {
40
40
  if (mode === "compact") {
41
- return "One summary line per assistant round; expanded edit/write rows show rich diffs.";
41
+ return "(Experimental) One summary line per assistant round; expanded edit/write rows show rich diffs.";
42
42
  }
43
43
  if (mode === "off") {
44
44
  return "Pi native tool rendering. Diff options below still apply independently.";
@@ -209,8 +209,8 @@ export async function showCcstylePanel(
209
209
  id: "mode",
210
210
  label: "Mode",
211
211
  description: modeSettingDescription(config.mode),
212
- currentValue: config.mode,
213
- values: ["on", "compact", "off"],
212
+ currentValue: config.mode === "compact" ? "compact (Experimental)" : config.mode,
213
+ values: ["on", "compact (Experimental)", "off"],
214
214
  };
215
215
  // Tracks whether the Exclude-tools submenu is open so Tab switches sections
216
216
  // only at the top level (mirrors Zentui settings: Tab = switch sections).
@@ -336,10 +336,14 @@ export async function showCcstylePanel(
336
336
 
337
337
  const onSettingChange = (id: string, value: string) => {
338
338
  switch (id) {
339
- case "mode":
340
- modeSetting.description = modeSettingDescription(value as CompactStyleMode);
341
- hooks.applyStyleMode(value as CompactStyleMode, ctx, toolGrouping);
339
+ case "mode": {
340
+ // 选项值带 Experimental 标记,选择后还原为真实 mode 值。
341
+ const mode: CompactStyleMode =
342
+ value === "compact (Experimental)" ? "compact" : (value as CompactStyleMode);
343
+ modeSetting.description = modeSettingDescription(mode);
344
+ hooks.applyStyleMode(mode, ctx, toolGrouping);
342
345
  return;
346
+ }
343
347
  case "excludeRenderers":
344
348
  excludeSetting.currentValue = formatExcludeRenderers(config.excludeRenderers);
345
349
  excludeSetting.description = excludeRenderersDescription(config.excludeRenderers);
@@ -10,13 +10,17 @@
10
10
  import { Markdown } from "@earendil-works/pi-tui";
11
11
  import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
12
12
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
- import { bindAgentSummary, summaryMarkdown } from "../utils/agent-summary.ts";
13
+ import {
14
+ bindAgentSummary,
15
+ summaryMarkdown,
16
+ type AgentSummaryData,
17
+ } from "../utils/agent-summary.ts";
14
18
 
15
19
  export const AGENT_SUMMARY_ENTRY_TYPE = "agent-summary";
16
20
 
17
21
  export default function (pi: ExtensionAPI): void {
18
22
  pi.registerEntryRenderer(AGENT_SUMMARY_ENTRY_TYPE, (entry, _options, theme) => {
19
- const line = summaryMarkdown(entry.data, true, {
23
+ const line = summaryMarkdown(entry.data as AgentSummaryData, true, {
20
24
  success: theme.getFgAnsi("success"),
21
25
  failed: theme.getFgAnsi("error"),
22
26
  });
@@ -13,6 +13,14 @@ import {
13
13
  } from "@earendil-works/pi-coding-agent";
14
14
  import { getKeybindings } from "@earendil-works/pi-tui";
15
15
  import { Markdown, Spacer, Text, type Component } from "@earendil-works/pi-tui";
16
+ import {
17
+ animateCompactThinkingText,
18
+ formatThoughtDuration,
19
+ styleCompactThinkingText,
20
+ } from "../renderer/compact-mode.ts";
21
+ import { refreshMountedTranscript } from "../renderer/transcript-refresh.ts";
22
+ // 保持导出兼容:渲染函数已并入 renderer/compact-mode.ts,这里 re-export。
23
+ export { animateCompactThinkingText, formatThoughtDuration, styleCompactThinkingText };
16
24
 
17
25
  // pi-tui 类型声明中 TUI 的 re-export 解析失败,本地用最小结构化类型(只用到 requestRender)。
18
26
  type RenderTui = { requestRender(force?: boolean): void };
@@ -146,70 +154,6 @@ function restoreDurationEntries(
146
154
  }
147
155
  }
148
156
 
149
- function formatThoughtDuration(durationMs: number) {
150
- if (durationMs < 1_000) {
151
- return `${Math.max(1, Math.round(durationMs))}ms`;
152
- }
153
-
154
- const totalSeconds = Math.max(1, Math.round(durationMs / 1_000));
155
- const minutes = Math.floor(totalSeconds / 60);
156
- const seconds = totalSeconds % 60;
157
- if (minutes === 0) return `${seconds}s`;
158
- return seconds === 0 ? `${minutes}m` : `${minutes}m ${seconds}s`;
159
- }
160
-
161
- export { formatThoughtDuration };
162
-
163
- type CompactThinkingTheme = Pick<Theme, "fg" | "italic" | "bold">;
164
-
165
- /** 与 compact-thinking 主渲染器共用的静态文字样式。 */
166
- export function styleCompactThinkingText(
167
- text: string,
168
- theme: CompactThinkingTheme | undefined,
169
- bold = false,
170
- ): string {
171
- if (!theme) return text;
172
- const colored = typeof theme.fg === "function" ? theme.fg("thinkingText", text) : text;
173
- const weighted = bold && typeof theme.bold === "function" ? theme.bold(colored) : colored;
174
- return typeof theme.italic === "function" ? theme.italic(weighted) : weighted;
175
- }
176
-
177
- /** 与 compact-thinking 主渲染器共用的活动思考扫光动画。 */
178
- export function animateCompactThinkingText(
179
- text: string,
180
- theme: CompactThinkingTheme | undefined,
181
- animationFrame: number,
182
- boldBase = false,
183
- ): string {
184
- if (!theme) return text;
185
- const characters = Array.from(text);
186
- if (characters.length === 0) return "";
187
- const highlightWidth = Math.max(1, Math.min(5, Math.ceil(characters.length * 0.28)));
188
- const start = (animationFrame % (characters.length + highlightWidth)) - highlightWidth;
189
- const end = start + highlightWidth;
190
- const before = characters.slice(0, Math.max(0, start)).join("");
191
- const highlighted = characters
192
- .slice(Math.max(0, start), Math.min(characters.length, end))
193
- .join("");
194
- const after = characters.slice(Math.max(0, end)).join("");
195
- const highlightedColored =
196
- highlighted && typeof theme.fg === "function" ? theme.fg("text", highlighted) : highlighted;
197
- const highlightedWeighted =
198
- highlightedColored && typeof theme.bold === "function"
199
- ? theme.bold(highlightedColored)
200
- : highlightedColored;
201
- const highlightedText =
202
- highlightedWeighted && typeof theme.italic === "function"
203
- ? theme.italic(highlightedWeighted)
204
- : highlightedWeighted;
205
-
206
- return (
207
- styleCompactThinkingText(before, theme, boldBase) +
208
- highlightedText +
209
- styleCompactThinkingText(after, theme, boldBase)
210
- );
211
- }
212
-
213
157
  function getThinkingToggleHint() {
214
158
  const keys = getKeybindings().getKeys("app.thinking.toggle");
215
159
  return keys.length > 0 ? `${keys.join("/")} to expand` : undefined;
@@ -343,7 +287,6 @@ function compactThinking(pi: ExtensionAPI) {
343
287
  };
344
288
 
345
289
  const completedDurations = new Map<number, Map<number, number>>();
346
- const renderedComponents = new Set<AssistantMessageComponentLike>();
347
290
  const streamingComponents = new Set<AssistantMessageComponentLike>();
348
291
  let activeThinking: ActiveThinking | undefined;
349
292
  let activeTheme: Theme | undefined;
@@ -355,14 +298,6 @@ function compactThinking(pi: ExtensionAPI) {
355
298
  let compactSummaryActive = false;
356
299
  let patchInstalled = true;
357
300
 
358
- function refreshRenderedComponents() {
359
- for (const component of renderedComponents) {
360
- const self = component as unknown as AssistantInternals;
361
- if (self.lastMessage) self.updateContent(self.lastMessage);
362
- }
363
- activeTui?.requestRender(true);
364
- }
365
-
366
301
  function thinkingStyle(text: string) {
367
302
  return styleCompactThinkingText(text, activeTheme);
368
303
  }
@@ -403,7 +338,6 @@ function compactThinking(pi: ExtensionAPI) {
403
338
  const component = this as AssistantMessageComponentLike;
404
339
  const self = this as unknown as AssistantInternals;
405
340
  self.lastMessage = message;
406
- renderedComponents.add(component);
407
341
  latestComponent = component;
408
342
  latestComponentTimestamp = message.timestamp;
409
343
 
@@ -512,8 +446,10 @@ function compactThinking(pi: ExtensionAPI) {
512
446
  (durationText ? thinkingStyle(` · ${durationText}`) : "");
513
447
  } else {
514
448
  // Completed compact blocks use one provider-independent status line.
449
+ // 无时长记录(中断流/旧会话/entry 缺失或索引错位)时不回退到加载文案
450
+ // "Thinking...":改用摘要标题或中性 "Thought"。
515
451
  heading = thinkingStyle(
516
- durationText ? `Thought for ${durationText}` : self.hiddenThinkingLabel || "Thinking...",
452
+ durationText ? `Thought for ${durationText}` : (latestSummary?.title ?? "Thought"),
517
453
  );
518
454
  }
519
455
  self.contentContainer.addChild(new Text(heading, self.outputPad, 0));
@@ -684,7 +620,7 @@ function compactThinking(pi: ExtensionAPI) {
684
620
  content?.type === "toolCall" &&
685
621
  (content.name === "Agent" ||
686
622
  content.name === "Agents" ||
687
- content.args?.subagent_type != null),
623
+ content.arguments?.subagent_type != null),
688
624
  )
689
625
  );
690
626
  }
@@ -728,7 +664,9 @@ function compactThinking(pi: ExtensionAPI) {
728
664
  // OpenAI-compatible providers may not close reasoning until the response ends.
729
665
  pi.on("tool_execution_start", (event: any) => {
730
666
  if (event.toolName === "Agent" || event.toolName === "Agents") {
731
- resumeAgentThinking(latestComponent?.lastMessage ?? undefined);
667
+ const lastMessage = (latestComponent as unknown as { lastMessage?: AssistantMessage })
668
+ ?.lastMessage;
669
+ resumeAgentThinking(lastMessage);
732
670
  } else {
733
671
  finishThinking();
734
672
  }
@@ -751,20 +689,24 @@ function compactThinking(pi: ExtensionAPI) {
751
689
 
752
690
  // An empty widget gives the animation loop access to requestRender without
753
691
  // enabling terminal mouse reporting or intercepting native scrollback input.
692
+ // setWidget 的 factory 同步执行,此刻补丁已安装。
754
693
  ctx.ui.setWidget(WIDGET_ID, (tui) => {
755
694
  activeTui = tui;
756
695
  return { render: () => [], invalidate() {} };
757
696
  });
758
697
 
759
- // On resume, Pi may construct the chat before session_start is emitted.
760
- // Rebuild those already-rendered components now that persisted durations
761
- // and the active theme are available.
762
- refreshRenderedComponents();
698
+ // pi 在 reload/resume 时先于 session_start 用原始原型重建聊天组件
699
+ // (rebuildChatFromMessages / renderCurrentSessionState)。由共享的
700
+ // refreshMountedTranscript 扫描挂载树重绘这些组件,恢复 compact 渲染、
701
+ // 持久化时长与工具调用显示。
702
+ refreshMountedTranscript(activeTui);
703
+ activeTui?.requestRender(true);
763
704
  });
764
705
 
765
706
  pi.on("session_tree", (_event, ctx) => {
766
707
  restoreDurationEntries(ctx.sessionManager.getBranch(), completedDurations);
767
- refreshRenderedComponents();
708
+ refreshMountedTranscript(activeTui);
709
+ activeTui?.requestRender(true);
768
710
  });
769
711
 
770
712
  pi.on("session_shutdown", (_event, ctx) => {
@@ -781,7 +723,6 @@ function compactThinking(pi: ExtensionAPI) {
781
723
  latestComponent = undefined;
782
724
  latestComponentTimestamp = undefined;
783
725
  completedDurations.clear();
784
- renderedComponents.clear();
785
726
  streamingComponents.clear();
786
727
  if (ctx.mode === "tui") ctx.ui.setWidget(WIDGET_ID, undefined);
787
728
 
@@ -639,7 +639,7 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
639
639
  );
640
640
 
641
641
  if (!action) break;
642
- const preview = previewByKey.get(action);
642
+ const preview = previewByKey.get(action as PreviewKey);
643
643
  if (!preview) continue;
644
644
 
645
645
  await showTextPreview(ctx, preview.title, preview.content);
@@ -129,6 +129,123 @@ function linkifyUrls(markdown: string): string {
129
129
  return out.join("\n");
130
130
  }
131
131
 
132
+ // ============================================================================
133
+ // 圈数字 → 半角括号
134
+ // ============================================================================
135
+
136
+ // Nerd Font 补丁字形(U+2460-U+2473)ink 超界,DirectWrite 下渲染会压住相邻字符;
137
+ // 转成 ASCII "(n)" 后任何字体下宽度一致。U+2460-U+2473 连续,差值即序号。
138
+ const CIRCLED_RE = /[①-⑳]/g;
139
+ const BLOCKQUOTE_PREFIX = /^ {0,3}>[ \t]?/;
140
+ const CODE_FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;
141
+ const CODE_FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*$/;
142
+
143
+ type FenceLine = { blockquoteDepth: number; content: string };
144
+
145
+ function parseFenceLine(line: string): FenceLine {
146
+ let content = line;
147
+ let blockquoteDepth = 0;
148
+ while (BLOCKQUOTE_PREFIX.test(content)) {
149
+ content = content.replace(BLOCKQUOTE_PREFIX, "");
150
+ blockquoteDepth++;
151
+ }
152
+ return { blockquoteDepth, content };
153
+ }
154
+
155
+ function replaceCircled(text: string): string {
156
+ return text.replace(CIRCLED_RE, (ch) => `(${ch.codePointAt(0)! - 0x2460 + 1})`);
157
+ }
158
+
159
+ function backtickRunAt(
160
+ text: string,
161
+ start: number,
162
+ skipEscaped: boolean,
163
+ ): { start: number; end: number } | null {
164
+ let index = text.indexOf("`", start);
165
+ while (index >= 0) {
166
+ let backslashes = 0;
167
+ for (let cursor = index - 1; cursor >= 0 && text[cursor] === "\\"; cursor--) backslashes++;
168
+ if (!skipEscaped || backslashes % 2 === 0) {
169
+ let end = index + 1;
170
+ while (text[end] === "`") end++;
171
+ return { start: index, end };
172
+ }
173
+ index = text.indexOf("`", index + 1);
174
+ }
175
+ return null;
176
+ }
177
+
178
+ /** 转换连续 prose(可跨行),但保留有效 inline code spans。 */
179
+ function deCircledProse(prose: string): string {
180
+ let cursor = 0;
181
+ let transformed = "";
182
+ while (cursor < prose.length) {
183
+ const opening = backtickRunAt(prose, cursor, true);
184
+ if (!opening) {
185
+ transformed += replaceCircled(prose.slice(cursor));
186
+ break;
187
+ }
188
+ let closing = backtickRunAt(prose, opening.end, false);
189
+ while (closing && closing.end - closing.start !== opening.end - opening.start) {
190
+ closing = backtickRunAt(prose, closing.end, false);
191
+ }
192
+ if (!closing) {
193
+ transformed += replaceCircled(prose.slice(cursor, opening.end));
194
+ cursor = opening.end;
195
+ continue;
196
+ }
197
+ transformed += replaceCircled(prose.slice(cursor, opening.start));
198
+ transformed += prose.slice(opening.start, closing.end);
199
+ cursor = closing.end;
200
+ }
201
+ return transformed;
202
+ }
203
+
204
+ /** 转换普通 Markdown 文本,但保留匹配 container 的 fenced blocks 与 inline code spans。 */
205
+ function deCircled(markdown: string): string {
206
+ const output: string[] = [];
207
+ let prose: string[] = [];
208
+ let fence: { marker: string; blockquoteDepth: number } | undefined;
209
+ const flushProse = () => {
210
+ if (prose.length === 0) return;
211
+ output.push(deCircledProse(prose.join("\n")));
212
+ prose = [];
213
+ };
214
+
215
+ for (const line of markdown.split("\n")) {
216
+ const parsed = parseFenceLine(line);
217
+ if (fence && parsed.blockquoteDepth >= fence.blockquoteDepth) {
218
+ output.push(line);
219
+ const close = parsed.content.match(CODE_FENCE_CLOSE)?.[1];
220
+ if (
221
+ parsed.blockquoteDepth === fence.blockquoteDepth &&
222
+ close?.[0] === fence.marker[0] &&
223
+ close.length >= fence.marker.length
224
+ ) {
225
+ fence = undefined;
226
+ }
227
+ continue;
228
+ }
229
+ fence = undefined;
230
+
231
+ if (parsed.content.trim() === "") {
232
+ flushProse();
233
+ output.push(line);
234
+ continue;
235
+ }
236
+ const open = parsed.content.match(CODE_FENCE_OPEN)?.[1];
237
+ if (open) {
238
+ flushProse();
239
+ fence = { marker: open, blockquoteDepth: parsed.blockquoteDepth };
240
+ output.push(line);
241
+ continue;
242
+ }
243
+ prose.push(line);
244
+ }
245
+ flushProse();
246
+ return output.join("\n");
247
+ }
248
+
132
249
  // ============================================================================
133
250
  // 注册
134
251
  // ============================================================================
@@ -140,6 +257,8 @@ export default function (pi: ExtensionAPI): void {
140
257
  const { messageType, isStreaming = false } = context ?? {};
141
258
  // 与官方推荐一致:thinking 块与流式中间态不转换
142
259
  if (messageType === "assistant-thinking" || isStreaming) return markdown;
260
+ // 0. 圈数字转半角括号(Nerd Font 补丁字形缺陷规避)
261
+ markdown = deCircled(markdown);
143
262
  // 1. Mermaid 方言渲染
144
263
  markdown = renderDiagrams(markdown, context);
145
264
  // 2. GitHub 风格提示框
@@ -12,17 +12,17 @@
12
12
  * 补丁生命周期遵循仓库既有模式:Symbol 所有权、dispose 仅恢复仍由本安装持有的
13
13
  * 方法、重入守卫防止 /reload 后残留闭包递归。
14
14
  */
15
- import { AssistantMessageComponent, ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
15
+ import {
16
+ AssistantMessageComponent,
17
+ ToolExecutionComponent,
18
+ type Theme,
19
+ } from "@earendil-works/pi-coding-agent";
16
20
  import * as PiTui from "@earendil-works/pi-tui";
17
21
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
18
22
  import { config, getToolDisplayConfig } from "../config/config.ts";
19
- import {
20
- animateCompactThinkingText,
21
- formatThoughtDuration,
22
- styleCompactThinkingText,
23
- } from "../feature/compact-thinking.ts";
24
23
  import { toolLoadingIcon } from "../utils/tool-loading-icon.ts";
25
24
  import { sanitizeToolResultText } from "../utils/tool-result-sanitize.ts";
25
+ import { refreshTranscriptComponent } from "./transcript-refresh.ts";
26
26
  import { getMessageDisplayTheme } from "./message-display.ts";
27
27
  import { showMoreHintText } from "./show-more-hint.ts";
28
28
  import {
@@ -60,6 +60,70 @@ const ASSISTANT_REENTRY_KEY = Symbol.for(ASSISTANT_REENTRY_DESCRIPTION);
60
60
  const Box = (PiTui as any).Box;
61
61
  const EDIT_WRITE_TOOLS = new Set(["edit", "write"]);
62
62
 
63
+ type CompactThinkingTheme = Pick<Theme, "fg" | "italic" | "bold">;
64
+
65
+ /** 与 compact-thinking 主渲染器共用的静态文字样式。 */
66
+ export function styleCompactThinkingText(
67
+ text: string,
68
+ theme: CompactThinkingTheme | undefined,
69
+ bold = false,
70
+ ): string {
71
+ if (!theme) return text;
72
+ const colored = typeof theme.fg === "function" ? theme.fg("thinkingText", text) : text;
73
+ const weighted = bold && typeof theme.bold === "function" ? theme.bold(colored) : colored;
74
+ return typeof theme.italic === "function" ? theme.italic(weighted) : weighted;
75
+ }
76
+
77
+ /** 与 compact-thinking 主渲染器共用的活动思考扫光动画。 */
78
+ export function animateCompactThinkingText(
79
+ text: string,
80
+ theme: CompactThinkingTheme | undefined,
81
+ animationFrame: number,
82
+ boldBase = false,
83
+ ): string {
84
+ if (!theme) return text;
85
+ const characters = Array.from(text);
86
+ if (characters.length === 0) return "";
87
+ const highlightWidth = Math.max(1, Math.min(5, Math.ceil(characters.length * 0.28)));
88
+ const start = (animationFrame % (characters.length + highlightWidth)) - highlightWidth;
89
+ const end = start + highlightWidth;
90
+ const before = characters.slice(0, Math.max(0, start)).join("");
91
+ const highlighted = characters
92
+ .slice(Math.max(0, start), Math.min(characters.length, end))
93
+ .join("");
94
+ const after = characters.slice(Math.max(0, end)).join("");
95
+ const highlightedColored =
96
+ highlighted && typeof theme.fg === "function" ? theme.fg("text", highlighted) : highlighted;
97
+ const highlightedWeighted =
98
+ highlightedColored && typeof theme.bold === "function"
99
+ ? theme.bold(highlightedColored)
100
+ : highlightedColored;
101
+ const highlightedText =
102
+ highlightedWeighted && typeof theme.italic === "function"
103
+ ? theme.italic(highlightedWeighted)
104
+ : highlightedWeighted;
105
+
106
+ return (
107
+ styleCompactThinkingText(before, theme, boldBase) +
108
+ highlightedText +
109
+ styleCompactThinkingText(after, theme, boldBase)
110
+ );
111
+ }
112
+
113
+ function formatThoughtDuration(durationMs: number) {
114
+ if (durationMs < 1_000) {
115
+ return `${Math.max(1, Math.round(durationMs))}ms`;
116
+ }
117
+
118
+ const totalSeconds = Math.max(1, Math.round(durationMs / 1_000));
119
+ const minutes = Math.floor(totalSeconds / 60);
120
+ const seconds = totalSeconds % 60;
121
+ if (minutes === 0) return `${seconds}s`;
122
+ return seconds === 0 ? `${minutes}m` : `${minutes}m ${seconds}s`;
123
+ }
124
+
125
+ export { formatThoughtDuration };
126
+
63
127
  /**
64
128
  * 逐条 assistant message 的摘要文本(无工具计数时可为空串):
65
129
  * 思考时长在前,工具按消息内首次出现顺序排列;read 按非空路径去重。
@@ -864,15 +928,15 @@ function refreshTrackedComponents(): void {
864
928
  for (const component of [...trackedAssistantComponents]) {
865
929
  try {
866
930
  if (config.mode !== "compact") detachAssistantExpansion(component);
867
- if (component.lastMessage) component.updateContent?.(component.lastMessage);
868
- else component.invalidate?.();
931
+ refreshTranscriptComponent(component);
869
932
  } catch {
870
933
  trackedAssistantComponents.delete(component);
871
934
  }
872
935
  }
873
936
  for (const component of [...trackedToolComponents]) {
874
937
  try {
875
- component.updateDisplay?.();
938
+ // 共享实现负责 updateDisplay;invalidate 属于 compact-mode 的跟踪语义。
939
+ refreshTranscriptComponent(component);
876
940
  component.invalidate?.();
877
941
  } catch {
878
942
  trackedToolComponents.delete(component);
@@ -771,7 +771,7 @@ function installToolExpandedBackground(): () => void {
771
771
  const host = globalThis as any;
772
772
  const previous = host[TOOL_EXPANDED_BACKGROUND_PATCH] as ToolExpandedBackgroundPatch | undefined;
773
773
  if (previous) previous.dispose();
774
- const prototype = ToolExecutionComponent.prototype;
774
+ const prototype = ToolExecutionComponent.prototype as unknown as { updateDisplay: () => void };
775
775
  const original = prototype.updateDisplay;
776
776
  const patch: ToolExpandedBackgroundPatch = {
777
777
  active: true,
@@ -888,7 +888,7 @@ export default function (
888
888
  compactThinking,
889
889
  );
890
890
  } else {
891
- applyStyleMode(arg, ctx, hooks.toolGrouping);
891
+ applyStyleMode(arg as CompactStyleMode, ctx, hooks.toolGrouping);
892
892
  }
893
893
  return;
894
894
  }