@herbertgao/pi-extensions 2026.9.19 → 2026.9.20

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 (186) hide show
  1. package/README.md +1 -8
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +76 -59
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +76 -59
  4. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/preview.webp +0 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +93 -34
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +346 -117
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +6 -36
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/index.ts +2 -2
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -29
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +330 -360
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/index.ts +56 -108
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/session.ts +97 -15
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/subagent.ts +80 -45
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/flush-docked-bash.ts +36 -0
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer-layout.ts +229 -0
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +479 -0
  17. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/startup-header.ts +92 -61
  18. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +58 -38
  19. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +19 -3
  20. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +1080 -229
  21. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +232 -259
  22. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +38 -48
  23. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +8 -2
  24. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +2 -7
  25. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +277 -113
  26. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +15 -6
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +18 -72
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +61 -26
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/ansi-utils.ts +25 -57
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-component.ts +125 -0
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-edit-render.ts +227 -0
  32. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-header.ts +136 -0
  33. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-highlight.ts +115 -0
  34. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-inline.ts +324 -0
  35. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-layout.ts +762 -0
  36. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-limits.ts +152 -0
  37. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-palette.ts +410 -0
  38. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-parse.ts +535 -0
  39. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
  40. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +21 -3219
  41. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-text.ts +44 -0
  42. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +568 -0
  43. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +19 -18
  44. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +2 -2
  45. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-execution.ts +1 -0
  46. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +142 -192
  47. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/mcp-title.ts +39 -0
  48. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{message-display.ts → tool/message-display.ts} +24 -90
  49. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +426 -0
  50. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +90 -132
  51. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{show-more-hint.ts → tool/show-more-hint.ts} +10 -0
  52. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +8 -26
  53. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-color.ts +44 -0
  54. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-text.ts +76 -0
  55. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/component-tree.ts +34 -0
  56. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/format.ts +28 -0
  57. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/fullscreen-detect.ts +51 -0
  58. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/patch-keys.ts +124 -0
  59. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/sgr-mouse.ts +70 -0
  60. package/node_modules/@herbertgao/pi-cc-extensions/package.json +7 -7
  61. package/package.json +3 -7
  62. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.en.svg +0 -101
  63. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.svg +0 -101
  64. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/welcome.webp +0 -0
  65. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/render-utils.ts +0 -5
  66. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +0 -30
  67. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-display-utils.ts +0 -12
  68. package/node_modules/pi-footer/CHANGELOG.md +0 -145
  69. package/node_modules/pi-footer/LICENSE +0 -21
  70. package/node_modules/pi-footer/README.md +0 -354
  71. package/node_modules/pi-footer/package.json +0 -68
  72. package/node_modules/pi-footer/src/cache.ts +0 -106
  73. package/node_modules/pi-footer/src/colors.ts +0 -237
  74. package/node_modules/pi-footer/src/config.ts +0 -202
  75. package/node_modules/pi-footer/src/event-widgets.ts +0 -41
  76. package/node_modules/pi-footer/src/extension-statuses.ts +0 -102
  77. package/node_modules/pi-footer/src/git.ts +0 -135
  78. package/node_modules/pi-footer/src/index.ts +0 -268
  79. package/node_modules/pi-footer/src/metrics.ts +0 -141
  80. package/node_modules/pi-footer/src/presets.ts +0 -383
  81. package/node_modules/pi-footer/src/render.ts +0 -114
  82. package/node_modules/pi-footer/src/separators.ts +0 -52
  83. package/node_modules/pi-footer/src/types.ts +0 -122
  84. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +0 -22
  85. package/node_modules/pi-footer/src/ui/color-options.ts +0 -195
  86. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +0 -61
  87. package/node_modules/pi-footer/src/ui/edit-colors.ts +0 -13
  88. package/node_modules/pi-footer/src/ui/events.ts +0 -35
  89. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +0 -60
  90. package/node_modules/pi-footer/src/ui/extension-statuses.ts +0 -73
  91. package/node_modules/pi-footer/src/ui/fields.ts +0 -252
  92. package/node_modules/pi-footer/src/ui/global-menu.ts +0 -126
  93. package/node_modules/pi-footer/src/ui/helpers.ts +0 -44
  94. package/node_modules/pi-footer/src/ui/layout.ts +0 -17
  95. package/node_modules/pi-footer/src/ui/line-list.ts +0 -19
  96. package/node_modules/pi-footer/src/ui/model.ts +0 -64
  97. package/node_modules/pi-footer/src/ui/navigation.ts +0 -29
  98. package/node_modules/pi-footer/src/ui/option-edit.ts +0 -93
  99. package/node_modules/pi-footer/src/ui/overlay-render.ts +0 -103
  100. package/node_modules/pi-footer/src/ui/screen-context.ts +0 -18
  101. package/node_modules/pi-footer/src/ui/screen-controller.ts +0 -27
  102. package/node_modules/pi-footer/src/ui/screen-render.ts +0 -32
  103. package/node_modules/pi-footer/src/ui/screen-state.ts +0 -20
  104. package/node_modules/pi-footer/src/ui/screen.ts +0 -243
  105. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +0 -92
  106. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +0 -52
  107. package/node_modules/pi-footer/src/ui/screens/controller.ts +0 -12
  108. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +0 -68
  109. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +0 -115
  110. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +0 -61
  111. package/node_modules/pi-footer/src/ui/screens/global.ts +0 -75
  112. package/node_modules/pi-footer/src/ui/screens/line-list.ts +0 -108
  113. package/node_modules/pi-footer/src/ui/screens/main.ts +0 -99
  114. package/node_modules/pi-footer/src/ui/screens/terminal.ts +0 -121
  115. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +0 -154
  116. package/node_modules/pi-footer/src/ui/terminal-menu.ts +0 -32
  117. package/node_modules/pi-footer/src/ui/theme.ts +0 -48
  118. package/node_modules/pi-footer/src/ui/title-bar.ts +0 -100
  119. package/node_modules/pi-footer/src/ui/widget-actions.ts +0 -76
  120. package/node_modules/pi-footer/src/ui.ts +0 -66
  121. package/node_modules/pi-footer/src/widgets/context.ts +0 -28
  122. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +0 -17
  123. package/node_modules/pi-footer/src/widgets/core/context-window.ts +0 -24
  124. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +0 -19
  125. package/node_modules/pi-footer/src/widgets/core/cwd.ts +0 -108
  126. package/node_modules/pi-footer/src/widgets/core/event.ts +0 -34
  127. package/node_modules/pi-footer/src/widgets/core/external-status.ts +0 -102
  128. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +0 -20
  129. package/node_modules/pi-footer/src/widgets/core/model.ts +0 -35
  130. package/node_modules/pi-footer/src/widgets/core/provider.ts +0 -17
  131. package/node_modules/pi-footer/src/widgets/core/session-name.ts +0 -17
  132. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +0 -17
  133. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +0 -17
  134. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +0 -17
  135. package/node_modules/pi-footer/src/widgets/git/branch.ts +0 -81
  136. package/node_modules/pi-footer/src/widgets/git/clean.ts +0 -19
  137. package/node_modules/pi-footer/src/widgets/git/deletions.ts +0 -17
  138. package/node_modules/pi-footer/src/widgets/git/diff.ts +0 -38
  139. package/node_modules/pi-footer/src/widgets/git/insertions.ts +0 -17
  140. package/node_modules/pi-footer/src/widgets/git/remote.ts +0 -17
  141. package/node_modules/pi-footer/src/widgets/git/root.ts +0 -17
  142. package/node_modules/pi-footer/src/widgets/git/sha.ts +0 -17
  143. package/node_modules/pi-footer/src/widgets/git/staged.ts +0 -17
  144. package/node_modules/pi-footer/src/widgets/git/status.ts +0 -19
  145. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +0 -17
  146. package/node_modules/pi-footer/src/widgets/git/untracked.ts +0 -17
  147. package/node_modules/pi-footer/src/widgets/instance.ts +0 -118
  148. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +0 -18
  149. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +0 -32
  150. package/node_modules/pi-footer/src/widgets/layout/separator.ts +0 -57
  151. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +0 -31
  152. package/node_modules/pi-footer/src/widgets/options.ts +0 -122
  153. package/node_modules/pi-footer/src/widgets/project/runtime.ts +0 -361
  154. package/node_modules/pi-footer/src/widgets/registry.ts +0 -223
  155. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +0 -17
  156. package/node_modules/pi-footer/src/widgets/session/compactions.ts +0 -17
  157. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +0 -18
  158. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +0 -18
  159. package/node_modules/pi-footer/src/widgets/session/messages.ts +0 -19
  160. package/node_modules/pi-footer/src/widgets/session/session-id.ts +0 -17
  161. package/node_modules/pi-footer/src/widgets/session/session-start.ts +0 -18
  162. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +0 -17
  163. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +0 -19
  164. package/node_modules/pi-footer/src/widgets/session/total-time.ts +0 -18
  165. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +0 -17
  166. package/node_modules/pi-footer/src/widgets/store.ts +0 -26
  167. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +0 -55
  168. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +0 -18
  169. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +0 -18
  170. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +0 -108
  171. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +0 -26
  172. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +0 -29
  173. package/node_modules/pi-footer/src/widgets/tokens/context.ts +0 -29
  174. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +0 -51
  175. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +0 -25
  176. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +0 -18
  177. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +0 -25
  178. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +0 -18
  179. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +0 -20
  180. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +0 -25
  181. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +0 -18
  182. package/node_modules/pi-footer/src/widgets/types.ts +0 -181
  183. package/node_modules/pi-footer/src/widgets/utils/colors.ts +0 -11
  184. package/node_modules/pi-footer/src/widgets/utils/context.ts +0 -112
  185. package/node_modules/pi-footer/src/widgets/utils/session.ts +0 -19
  186. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +0 -78
@@ -1,38 +1,38 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { getCompactRunStatusText } from "../../renderer/compact-mode.ts";
3
+ import {
4
+ fullscreenLazyTui,
5
+ getToolMouseTui,
6
+ isFullscreenAtBottom,
7
+ } from "../../renderer/mouse/scroll.ts";
8
+ import { formatDuration } from "../../utils/format.ts";
2
9
 
3
10
  const REFRESH_INTERVAL_MS = 1_000;
4
11
  /** Elapsed time is only shown once the turn has run this long. */
5
12
  const SHOW_TIMER_AFTER_MS = 3_000;
6
13
 
7
- function formatDuration(ms: number): string {
8
- const totalSec = Math.floor(ms / 1000);
9
- const hours = Math.floor(totalSec / 3600);
10
- const minutes = Math.floor((totalSec % 3600) / 60);
11
- const seconds = totalSec % 60;
12
- if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
13
- if (minutes > 0) return `${minutes}m ${seconds}s`;
14
- return `${seconds}s`;
15
- }
16
-
17
14
  function formatCount(value: number): string {
18
15
  return new Intl.NumberFormat("en-US").format(value);
19
16
  }
20
17
 
21
- function estimateTextLength(message: any): number {
22
- if (!Array.isArray(message?.content)) return 0;
23
- return message.content.reduce((sum: number, block: any) => {
24
- if (block?.type === "text" && typeof block.text === "string") return sum + block.text.length;
25
- if (block?.type === "thinking" && block.thinkingSignature?.body)
26
- return sum + (block.thinkingSignature.body as string).length;
27
- return sum;
28
- }, 0);
29
- }
30
-
31
- function textBlockLengths(message: any): number[] {
32
- if (!Array.isArray(message?.content)) return [];
18
+ type ContentBlock = {
19
+ type?: unknown;
20
+ text?: unknown;
21
+ thinkingSignature?: { body?: unknown };
22
+ };
23
+
24
+ type StreamMessage = {
25
+ content?: unknown;
26
+ usage?: { output?: unknown };
27
+ };
28
+
29
+ /** 每个 content index 的可见文本/思考长度;无对应块的 index 保持稀疏洞。 */
30
+ function textBlockLengths(message: StreamMessage): number[] {
31
+ const content = message.content;
32
+ if (!Array.isArray(content)) return [];
33
33
  const lengths: number[] = [];
34
- for (let index = 0; index < message.content.length; index++) {
35
- const block = message.content[index];
34
+ for (let index = 0; index < content.length; index++) {
35
+ const block = content[index] as ContentBlock;
36
36
  if (block?.type === "text" && typeof block.text === "string") {
37
37
  lengths[index] = block.text.length;
38
38
  } else if (block?.type === "thinking" && block.thinkingSignature?.body) {
@@ -42,11 +42,15 @@ function textBlockLengths(message: any): number[] {
42
42
  return lengths;
43
43
  }
44
44
 
45
- function outputUsage(message: any): number {
45
+ function outputUsage(message: StreamMessage): number {
46
46
  const value = Number(message?.usage?.output);
47
47
  return Number.isFinite(value) && value > 0 ? Math.round(value) : 0;
48
48
  }
49
49
 
50
+ type WorkingUi = {
51
+ setWorkingMessage(message?: string): void;
52
+ };
53
+
50
54
  /**
51
55
  * Extend Pi's footer working row while preserving its spinner and "Working...":
52
56
  * `⠋ Working... (↓ 1,234 tokens · 12s)`
@@ -63,7 +67,7 @@ export default function (pi: ExtensionAPI): void {
63
67
  let providerOutputTokens = 0;
64
68
  let refreshTimer: ReturnType<typeof setTimeout> | null = null;
65
69
  let lastMessage: string | null = null;
66
- let activeCtx: { ui: any; hasUI: boolean } | null = null;
70
+ let activeCtx: { ui: WorkingUi | undefined; hasUI: boolean } | null = null;
67
71
 
68
72
  function tokenCount(): number {
69
73
  return providerOutputTokens || Math.max(0, Math.round(responseLength / 4));
@@ -75,26 +79,41 @@ export default function (pi: ExtensionAPI): void {
75
79
  responseLength = Math.max(0, responseLength + responseTextBlockLengths[index] - previous);
76
80
  }
77
81
 
78
- function resetResponseTracking(message?: any): void {
82
+ function resetResponseTracking(message?: StreamMessage): void {
79
83
  responseTextBlockLengths = message ? textBlockLengths(message) : [];
80
- responseLength = message ? estimateTextLength(message) : 0;
84
+ responseLength = responseTextBlockLengths.reduce((sum, length) => sum + length, 0);
81
85
  providerOutputTokens = message ? outputUsage(message) : 0;
82
86
  }
83
87
 
84
- function updateProviderUsage(message: any): void {
88
+ function updateProviderUsage(message: StreamMessage): void {
85
89
  const output = outputUsage(message);
86
90
  if (output > 0) providerOutputTokens = output;
87
91
  }
88
92
 
93
+ /**
94
+ * 摘要行滚出视口才镜像:仅 fullscreen 且已离开 transcript 底部。
95
+ * regular 没有“离开底部”信号(transcript 在终端回滚区),保持 Pi 默认文案。
96
+ */
97
+ function compactMirrorText(): string | undefined {
98
+ const tui = getToolMouseTui();
99
+ if (!tui || !fullscreenLazyTui(tui) || isFullscreenAtBottom(tui)) return undefined;
100
+ return getCompactRunStatusText();
101
+ }
102
+
89
103
  function buildWorkingMessage(): string {
104
+ // turn_end 后迟到的 provider 事件不得再驱动 footer(否则渲染出 epoch 级计时)。
90
105
  if (!turnActive) return "";
91
- const startedAt = agentStartTime || turnStartTime;
92
- if (!startedAt) return "";
93
- const elapsed = Date.now() - startedAt;
94
- const tokens = tokenCount();
95
106
  const parts: string[] = [];
107
+ const tokens = tokenCount();
96
108
  if (tokens > 0) parts.push(`↓ ${formatCount(tokens)} tokens`);
97
- if (elapsed >= SHOW_TIMER_AFTER_MS || tokens > 0) parts.push(formatDuration(elapsed));
109
+ // compact 活动回合且已滚出摘要行:直接用摘要行文案(自带回合时长,不叠 agent 计时)。
110
+ const compactStatus = compactMirrorText();
111
+ if (compactStatus) return [compactStatus, ...parts].join(" · ");
112
+ const elapsed = Date.now() - (agentStartTime || turnStartTime);
113
+ if (elapsed >= SHOW_TIMER_AFTER_MS || tokens > 0) {
114
+ // formatDuration 低于 1 秒返回 "",此处回退 "0s" 保持计时器连续跳动。
115
+ parts.push(formatDuration(elapsed) || "0s");
116
+ }
98
117
  return parts.length ? `Working... (${parts.join(" · ")})` : "";
99
118
  }
100
119
 
@@ -102,7 +121,7 @@ export default function (pi: ExtensionAPI): void {
102
121
  try {
103
122
  return activeCtx?.hasUI === true;
104
123
  } catch {
105
- // A stale ctx after session replacement/reload throws from its getter; stop driving the footer.
124
+ // 会话替换/reload 后捕获的 ctx 失效,getter 抛错;停止驱动 footer。
106
125
  turnActive = false;
107
126
  activeCtx = null;
108
127
  stopRefreshLoop();
@@ -114,7 +133,7 @@ export default function (pi: ExtensionAPI): void {
114
133
  lastMessage = null;
115
134
  if (!workingUiAvailable()) return;
116
135
  try {
117
- activeCtx?.ui.setWorkingMessage();
136
+ activeCtx?.ui?.setWorkingMessage();
118
137
  } catch {
119
138
  // Noop when the TUI is unavailable.
120
139
  }
@@ -130,7 +149,7 @@ export default function (pi: ExtensionAPI): void {
130
149
  if (!force && next === lastMessage) return;
131
150
  lastMessage = next;
132
151
  try {
133
- activeCtx?.ui.setWorkingMessage(next);
152
+ activeCtx?.ui?.setWorkingMessage(next);
134
153
  } catch {
135
154
  // Noop when the TUI is unavailable.
136
155
  }
@@ -143,7 +162,7 @@ export default function (pi: ExtensionAPI): void {
143
162
  try {
144
163
  syncWorkingMessage();
145
164
  } catch {
146
- // An exception here would become an uncaughtException and kill Pi; stop the decorative refresh.
165
+ // 定时器内的异常会成为 uncaughtException 终止 Pi;装饰性刷新直接停止。
147
166
  turnActive = false;
148
167
  return;
149
168
  }
@@ -203,6 +222,7 @@ export default function (pi: ExtensionAPI): void {
203
222
  } else if (evt.type === "done") {
204
223
  resetResponseTracking(evt.message);
205
224
  } else if (evt.type === "error") {
225
+ // provider 错误事件的 usage.output 不可信,不得计入 token 统计。
206
226
  resetResponseTracking();
207
227
  } else {
208
228
  updateProviderUsage(evt.partial);
@@ -3,7 +3,9 @@ import { config } from "./config/config.ts";
3
3
 
4
4
  // shell
5
5
  import piAliases from "./feature/shell/aliases.ts";
6
- import piStartupHeader from "./feature/shell/startup-header.ts";
6
+ import { installFlushDockedBash } from "./feature/shell/flush-docked-bash.ts";
7
+ import customFooter from "./feature/shell/footer.ts";
8
+ import piStartupHeader, { installEarlyStartupHeader } from "./feature/shell/startup-header.ts";
7
9
  import workingMessage from "./feature/shell/working-message.ts";
8
10
 
9
11
  // feature
@@ -12,22 +14,36 @@ import agentSummary from "./feature/agent-summary/index.ts";
12
14
  import context from "./feature/context.ts";
13
15
  import sessionReference from "./feature/reference/index.ts";
14
16
  import {
15
- installCompactThinking,
16
17
  type CompactThinkingController,
18
+ installCompactThinking,
17
19
  } from "./feature/compact-thinking.ts";
18
20
 
19
21
  // renderer
20
22
  import claudeCodeStyle, { getCompactThinkingConfig } from "./renderer/index.ts";
21
23
  import markdownEnhance from "./renderer/markdown-enhance.ts";
24
+ import {
25
+ COMPACT_MODE_PATCH_KEY,
26
+ COMPACT_THINKING_OWNER,
27
+ patchRegistry,
28
+ } from "./utils/patch-keys.ts";
22
29
 
23
30
  export default function (pi: ExtensionAPI): void {
31
+ // /reload 或会话切换接手:先释放旧一代 compact 外层,再停旧 thinking
32
+ // owner(顺序不可反,否则新一代会把旧 wrapper 误采样为 native 原型,
33
+ // 补丁因此在 shutdown 后残留或叠加)。
34
+ patchRegistry.get<{ dispose(): void }>(COMPACT_MODE_PATCH_KEY)?.dispose();
35
+ patchRegistry.get<{ stop(): void }>(COMPACT_THINKING_OWNER)?.stop();
36
+
24
37
  // shell chrome
25
38
  if (config.enableAliases) piAliases(pi);
39
+ installFlushDockedBash();
40
+ installEarlyStartupHeader();
26
41
  piStartupHeader(pi);
27
42
  if (config.enableWorkingMessage) workingMessage(pi);
43
+ customFooter(pi);
28
44
 
29
45
  // Renderer 必须先注册 lifecycle handler,shutdown 时才能先解开外层 compact
30
- // patch;bridge 让稍后安装的 thinking controller 仍可直接供 renderer query。
46
+ // patch,thinking 随后才能恢复原生原型;bridge 转发给稍后安装的 controller。
31
47
  let compactThinking: CompactThinkingController | undefined;
32
48
  const compactThinkingQuery: CompactThinkingController = {
33
49
  updateConfig: (next) => compactThinking?.updateConfig(next),