@herbertgao/pi-extensions 2026.9.18 → 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 (226) hide show
  1. package/README.md +20 -28
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +76 -59
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +76 -59
  5. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/preview.webp +0 -0
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +93 -34
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +346 -117
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +6 -36
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/index.ts +2 -2
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -29
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +330 -360
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/index.ts +56 -108
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/session.ts +97 -15
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/subagent.ts +80 -45
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/flush-docked-bash.ts +36 -0
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer-layout.ts +229 -0
  17. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +479 -0
  18. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/startup-header.ts +92 -61
  19. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +75 -40
  20. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +19 -3
  21. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +1080 -229
  22. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +232 -259
  23. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +42 -41
  24. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +8 -2
  25. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +2 -7
  26. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +277 -113
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +15 -6
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +18 -72
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +61 -26
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/ansi-utils.ts +25 -57
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-component.ts +125 -0
  32. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-edit-render.ts +227 -0
  33. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-header.ts +136 -0
  34. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-highlight.ts +115 -0
  35. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-inline.ts +324 -0
  36. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-layout.ts +762 -0
  37. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-limits.ts +152 -0
  38. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-palette.ts +410 -0
  39. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-parse.ts +535 -0
  40. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
  41. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +21 -3219
  42. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-text.ts +44 -0
  43. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +568 -0
  44. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +47 -10
  45. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +2 -2
  46. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-execution.ts +1 -0
  47. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +145 -190
  48. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/mcp-title.ts +39 -0
  49. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{message-display.ts → tool/message-display.ts} +24 -90
  50. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +426 -0
  51. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +90 -132
  52. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{show-more-hint.ts → tool/show-more-hint.ts} +10 -0
  53. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +8 -26
  54. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-color.ts +44 -0
  55. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-text.ts +76 -0
  56. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/component-tree.ts +34 -0
  57. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/format.ts +28 -0
  58. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/fullscreen-detect.ts +51 -0
  59. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/patch-keys.ts +124 -0
  60. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/sgr-mouse.ts +70 -0
  61. package/node_modules/@herbertgao/pi-cc-extensions/package.json +8 -8
  62. package/node_modules/pi-antigravity/README.md +29 -6
  63. package/node_modules/pi-antigravity/package.json +12 -7
  64. package/node_modules/pi-antigravity/src/auth/accounts.ts +327 -0
  65. package/node_modules/pi-antigravity/src/auth/index.ts +9 -0
  66. package/node_modules/pi-antigravity/src/client/client.ts +1 -1
  67. package/node_modules/pi-antigravity/src/image/image.ts +4 -4
  68. package/node_modules/pi-antigravity/src/index.ts +203 -13
  69. package/node_modules/pi-antigravity/src/search/index.ts +1 -0
  70. package/node_modules/pi-antigravity/src/search/search.ts +325 -0
  71. package/node_modules/pi-antigravity/src/stream/stream.ts +191 -14
  72. package/node_modules/pi-antigravity/src/types/enums.ts +45 -36
  73. package/node_modules/pi-antigravity/src/types/types.ts +5 -12
  74. package/node_modules/pi-antigravity/src/utils/http.ts +7 -2
  75. package/node_modules/pi-antigravity/src/utils/util.ts +31 -3
  76. package/node_modules/pi-typesafe/README.md +2 -0
  77. package/node_modules/pi-typesafe/package.json +2 -1
  78. package/node_modules/pi-web-access/CHANGELOG.md +72 -0
  79. package/node_modules/pi-web-access/README.md +34 -13
  80. package/node_modules/pi-web-access/chrome-cookies.ts +79 -25
  81. package/node_modules/pi-web-access/curator-page.ts +5 -1
  82. package/node_modules/pi-web-access/curator-server.ts +2 -0
  83. package/node_modules/pi-web-access/dist/index.js +1337 -1009
  84. package/node_modules/pi-web-access/exa.ts +20 -52
  85. package/node_modules/pi-web-access/extract.ts +41 -4
  86. package/node_modules/pi-web-access/gemini-search.ts +12 -4
  87. package/node_modules/pi-web-access/index.ts +29 -37
  88. package/node_modules/pi-web-access/openai-search.ts +35 -7
  89. package/node_modules/pi-web-access/package.json +6 -5
  90. package/node_modules/pi-web-access/page-query.ts +2 -2
  91. package/node_modules/pi-web-access/query-rewrite.ts +2 -3
  92. package/node_modules/pi-web-access/ssrf-protection.ts +27 -10
  93. package/node_modules/pi-web-access/storage.ts +48 -77
  94. package/node_modules/pi-web-access/summary-model-scope.ts +6 -77
  95. package/node_modules/pi-web-access/summary-review.ts +3 -4
  96. package/node_modules/pi-web-access/tavily.ts +67 -20
  97. package/node_modules/pi-web-access/tool-activation.ts +53 -33
  98. package/node_modules/pi-web-access/utils.ts +1 -1
  99. package/node_modules/pi-web-access/you.ts +164 -0
  100. package/node_modules/pi-web-access/zai.ts +161 -0
  101. package/package.json +7 -11
  102. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.en.svg +0 -101
  103. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.svg +0 -101
  104. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/welcome.webp +0 -0
  105. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/render-utils.ts +0 -5
  106. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +0 -30
  107. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-display-utils.ts +0 -12
  108. package/node_modules/pi-footer/CHANGELOG.md +0 -145
  109. package/node_modules/pi-footer/LICENSE +0 -21
  110. package/node_modules/pi-footer/README.md +0 -354
  111. package/node_modules/pi-footer/package.json +0 -68
  112. package/node_modules/pi-footer/src/cache.ts +0 -106
  113. package/node_modules/pi-footer/src/colors.ts +0 -237
  114. package/node_modules/pi-footer/src/config.ts +0 -202
  115. package/node_modules/pi-footer/src/event-widgets.ts +0 -41
  116. package/node_modules/pi-footer/src/extension-statuses.ts +0 -102
  117. package/node_modules/pi-footer/src/git.ts +0 -135
  118. package/node_modules/pi-footer/src/index.ts +0 -268
  119. package/node_modules/pi-footer/src/metrics.ts +0 -141
  120. package/node_modules/pi-footer/src/presets.ts +0 -383
  121. package/node_modules/pi-footer/src/render.ts +0 -114
  122. package/node_modules/pi-footer/src/separators.ts +0 -52
  123. package/node_modules/pi-footer/src/types.ts +0 -122
  124. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +0 -22
  125. package/node_modules/pi-footer/src/ui/color-options.ts +0 -195
  126. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +0 -61
  127. package/node_modules/pi-footer/src/ui/edit-colors.ts +0 -13
  128. package/node_modules/pi-footer/src/ui/events.ts +0 -35
  129. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +0 -60
  130. package/node_modules/pi-footer/src/ui/extension-statuses.ts +0 -73
  131. package/node_modules/pi-footer/src/ui/fields.ts +0 -252
  132. package/node_modules/pi-footer/src/ui/global-menu.ts +0 -126
  133. package/node_modules/pi-footer/src/ui/helpers.ts +0 -44
  134. package/node_modules/pi-footer/src/ui/layout.ts +0 -17
  135. package/node_modules/pi-footer/src/ui/line-list.ts +0 -19
  136. package/node_modules/pi-footer/src/ui/model.ts +0 -64
  137. package/node_modules/pi-footer/src/ui/navigation.ts +0 -29
  138. package/node_modules/pi-footer/src/ui/option-edit.ts +0 -93
  139. package/node_modules/pi-footer/src/ui/overlay-render.ts +0 -103
  140. package/node_modules/pi-footer/src/ui/screen-context.ts +0 -18
  141. package/node_modules/pi-footer/src/ui/screen-controller.ts +0 -27
  142. package/node_modules/pi-footer/src/ui/screen-render.ts +0 -32
  143. package/node_modules/pi-footer/src/ui/screen-state.ts +0 -20
  144. package/node_modules/pi-footer/src/ui/screen.ts +0 -243
  145. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +0 -92
  146. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +0 -52
  147. package/node_modules/pi-footer/src/ui/screens/controller.ts +0 -12
  148. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +0 -68
  149. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +0 -115
  150. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +0 -61
  151. package/node_modules/pi-footer/src/ui/screens/global.ts +0 -75
  152. package/node_modules/pi-footer/src/ui/screens/line-list.ts +0 -108
  153. package/node_modules/pi-footer/src/ui/screens/main.ts +0 -99
  154. package/node_modules/pi-footer/src/ui/screens/terminal.ts +0 -121
  155. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +0 -154
  156. package/node_modules/pi-footer/src/ui/terminal-menu.ts +0 -32
  157. package/node_modules/pi-footer/src/ui/theme.ts +0 -48
  158. package/node_modules/pi-footer/src/ui/title-bar.ts +0 -100
  159. package/node_modules/pi-footer/src/ui/widget-actions.ts +0 -76
  160. package/node_modules/pi-footer/src/ui.ts +0 -66
  161. package/node_modules/pi-footer/src/widgets/context.ts +0 -28
  162. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +0 -17
  163. package/node_modules/pi-footer/src/widgets/core/context-window.ts +0 -24
  164. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +0 -19
  165. package/node_modules/pi-footer/src/widgets/core/cwd.ts +0 -108
  166. package/node_modules/pi-footer/src/widgets/core/event.ts +0 -34
  167. package/node_modules/pi-footer/src/widgets/core/external-status.ts +0 -102
  168. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +0 -20
  169. package/node_modules/pi-footer/src/widgets/core/model.ts +0 -35
  170. package/node_modules/pi-footer/src/widgets/core/provider.ts +0 -17
  171. package/node_modules/pi-footer/src/widgets/core/session-name.ts +0 -17
  172. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +0 -17
  173. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +0 -17
  174. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +0 -17
  175. package/node_modules/pi-footer/src/widgets/git/branch.ts +0 -81
  176. package/node_modules/pi-footer/src/widgets/git/clean.ts +0 -19
  177. package/node_modules/pi-footer/src/widgets/git/deletions.ts +0 -17
  178. package/node_modules/pi-footer/src/widgets/git/diff.ts +0 -38
  179. package/node_modules/pi-footer/src/widgets/git/insertions.ts +0 -17
  180. package/node_modules/pi-footer/src/widgets/git/remote.ts +0 -17
  181. package/node_modules/pi-footer/src/widgets/git/root.ts +0 -17
  182. package/node_modules/pi-footer/src/widgets/git/sha.ts +0 -17
  183. package/node_modules/pi-footer/src/widgets/git/staged.ts +0 -17
  184. package/node_modules/pi-footer/src/widgets/git/status.ts +0 -19
  185. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +0 -17
  186. package/node_modules/pi-footer/src/widgets/git/untracked.ts +0 -17
  187. package/node_modules/pi-footer/src/widgets/instance.ts +0 -118
  188. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +0 -18
  189. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +0 -32
  190. package/node_modules/pi-footer/src/widgets/layout/separator.ts +0 -57
  191. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +0 -31
  192. package/node_modules/pi-footer/src/widgets/options.ts +0 -122
  193. package/node_modules/pi-footer/src/widgets/project/runtime.ts +0 -361
  194. package/node_modules/pi-footer/src/widgets/registry.ts +0 -223
  195. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +0 -17
  196. package/node_modules/pi-footer/src/widgets/session/compactions.ts +0 -17
  197. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +0 -18
  198. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +0 -18
  199. package/node_modules/pi-footer/src/widgets/session/messages.ts +0 -19
  200. package/node_modules/pi-footer/src/widgets/session/session-id.ts +0 -17
  201. package/node_modules/pi-footer/src/widgets/session/session-start.ts +0 -18
  202. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +0 -17
  203. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +0 -19
  204. package/node_modules/pi-footer/src/widgets/session/total-time.ts +0 -18
  205. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +0 -17
  206. package/node_modules/pi-footer/src/widgets/store.ts +0 -26
  207. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +0 -55
  208. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +0 -18
  209. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +0 -18
  210. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +0 -108
  211. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +0 -26
  212. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +0 -29
  213. package/node_modules/pi-footer/src/widgets/tokens/context.ts +0 -29
  214. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +0 -51
  215. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +0 -25
  216. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +0 -18
  217. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +0 -25
  218. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +0 -18
  219. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +0 -20
  220. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +0 -25
  221. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +0 -18
  222. package/node_modules/pi-footer/src/widgets/types.ts +0 -181
  223. package/node_modules/pi-footer/src/widgets/utils/colors.ts +0 -11
  224. package/node_modules/pi-footer/src/widgets/utils/context.ts +0 -112
  225. package/node_modules/pi-footer/src/widgets/utils/session.ts +0 -19
  226. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +0 -78
@@ -1,11 +1,21 @@
1
- import { ThinkingPreviewBlock, thinkingRunKey } from "../../feature/compact-thinking.ts";
1
+ import { ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
2
2
  import { hasActiveTextPreview, showTextPreview } from "../../feature/context.ts";
3
+ import { ThinkingPreviewBlock } from "../../feature/compact-thinking.ts";
4
+ import {
5
+ patchRegistry,
6
+ TOOL_GROUPING_PARENT_KEY,
7
+ TOOL_MOUSE_OWNER_KEY,
8
+ } from "../../utils/patch-keys.ts";
3
9
  import { ToolGroupComponent } from "../tool/grouping.ts";
4
- import { isCompactAssistantComponent, setHoveredCompactAssistant } from "../compact-mode.ts";
5
- import { isMessageDisplayComponent } from "../message-display.ts";
10
+ import {
11
+ isCompactAssistantComponent,
12
+ markCompactRoundToolExpanded,
13
+ setHoveredCompactAssistant,
14
+ } from "../compact-mode.ts";
15
+ import { isMessageDisplayComponent } from "../tool/message-display.ts";
6
16
  import { config } from "../../config/config.ts";
7
17
  import { isLazyProxyTui } from "../../utils/fullscreen-detect.ts";
8
- import { setToolTuiFullscreen } from "../show-more-hint.ts";
18
+ import { setToolTuiFullscreen, isCollapseHintLine } from "../tool/show-more-hint.ts";
9
19
  import {
10
20
  type ExpandedToolIoView,
11
21
  getActiveIoViewFrame,
@@ -17,6 +27,7 @@ import {
17
27
  import {
18
28
  collectToolComponents,
19
29
  extractToolFramePlacements,
30
+ isSgrIdleMotion,
20
31
  isSgrLeftPress,
21
32
  isSgrLeftRelease,
22
33
  isToolExecutionComponent,
@@ -36,19 +47,18 @@ import {
36
47
  isScrollbarColumnAt,
37
48
  } from "./layout.ts";
38
49
  import {
39
- disableOfficialScrollToEnd,
40
50
  fullscreenLazyTui,
41
51
  hideScrollButton,
52
+ restoreOfficialScrollToEnd,
53
+ syncOfficialScrollToEnd,
42
54
  isScrollBottomInput,
43
55
  renderScrollButton,
44
56
  resetScrollButtonState,
45
- restoreOfficialScrollToEnd,
46
57
  scheduleScrollButtonSync,
47
58
  setScrollButtonHovered,
48
59
  setScrollButtonVisible,
49
60
  setScrollButtonWidget,
50
61
  setToolMouseTui,
51
- syncOfficialScrollToEnd,
52
62
  getScrollButtonVisible,
53
63
  getScrollButtonWidget,
54
64
  getToolMouseTui,
@@ -74,7 +84,7 @@ type FrameToolRender = {
74
84
  };
75
85
 
76
86
  type InteractionRegion = {
77
- kind: "collapsed-hint" | "expanded-card" | "show-more" | "scroll-bottom";
87
+ kind: "collapsed-hint" | "collapse-hint" | "expanded-card" | "show-more" | "scroll-bottom";
78
88
  row: number;
79
89
  startCol: number;
80
90
  endCol: number;
@@ -89,9 +99,7 @@ const TOOL_MOUSE_WIDGET_KEY = "ccstyle-tool-mouse";
89
99
  const TOOL_MOUSE_MOTION_ENABLE = "\x1b[?1003h\x1b[?1006h";
90
100
  const TOOL_MOUSE_MOTION_DISABLE = "\x1b[?1003l";
91
101
  const FULLSCREEN_MOTION_ENABLED = Symbol("ccstyle.fullscreen-motion-enabled");
92
- const TOOL_MOUSE_OWNER_KEY = Symbol.for("pi.ccstyle.tool-mouse-owner");
93
- type ToolMouseOwner = object;
94
- const DEFAULT_TOOL_MOUSE_OWNER: ToolMouseOwner = {};
102
+ const DEFAULT_TOOL_MOUSE_OWNER = {};
95
103
  export const TOOL_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1003l\x1b[?1000l";
96
104
 
97
105
  let toolMouseUi: any = null;
@@ -101,7 +109,7 @@ let toolMouseRenderPatchOriginal: ((...args: any[]) => any) | null = null;
101
109
  let toolMouseRenderPatchWrapper: ((...args: any[]) => any) | null = null;
102
110
  let toolMouseRenderPatchState: { active: boolean } | null = null;
103
111
  let toolMouseRawWrite: ((data: string) => unknown) | null = null;
104
- let toolMouseInstallationOwner: ToolMouseOwner | null = null;
112
+ let toolMouseInstallationOwner: object | null = null;
105
113
  let fullscreenMotionTerminal: any = null;
106
114
  let ownsFullscreenMotion = false;
107
115
  let sessionRenderTimer: ReturnType<typeof setTimeout> | null = null;
@@ -119,6 +127,7 @@ function interactionRegionAt(packet: SgrMousePacket): InteractionRegion | null {
119
127
  matches.find((region) => region.kind === "show-more") ??
120
128
  matches.find((region) => region.kind === "scroll-bottom") ??
121
129
  matches.find((region) => region.kind === "collapsed-hint") ??
130
+ matches.find((region) => region.kind === "collapse-hint") ??
122
131
  matches.find((region) => region.kind === "expanded-card") ??
123
132
  null
124
133
  );
@@ -140,13 +149,18 @@ function tryOpenToolIoShowMore(region: InteractionRegion): boolean {
140
149
  }
141
150
 
142
151
  function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
143
- if ((packet.code & 32) === 0 || packet.final !== "M") return;
152
+ if (!isSgrIdleMotion(packet)) return;
144
153
  const region = interactionRegionAt(packet);
145
154
  const nextScrollButtonHovered = region?.kind === "scroll-bottom";
146
155
  const scrollButtonChanged = setScrollButtonHovered(nextScrollButtonHovered);
147
156
  const component = region?.component;
148
157
  const nextToolCallId = region?.kind === "collapsed-hint" ? (component?.toolCallId ?? null) : null;
149
- const nextGroup = component instanceof ToolGroupComponent ? component : null;
158
+ // group 只在提示文字上高亮(展开卡整行都是 expanded-card,不能算 hint)。
159
+ const nextGroup =
160
+ (region?.kind === "collapsed-hint" || region?.kind === "collapse-hint") &&
161
+ component instanceof ToolGroupComponent
162
+ ? component
163
+ : null;
150
164
  const nextIoView = region?.kind === "show-more" ? (region.view ?? null) : null;
151
165
  const nextIoSection = region?.kind === "show-more" ? (region.section ?? null) : null;
152
166
  const changed = nextToolCallId !== sharedToolHoverState().toolCallId;
@@ -160,74 +174,92 @@ function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
160
174
  tui.requestRender?.();
161
175
  }
162
176
 
163
- const EXPAND_PANEL_DOUBLE_CLICK_MS = 400;
164
- const EXPAND_PANEL_CLICK_ARM_MS = 50;
165
- type ExpandPanelIdentity = string | object;
166
- let pendingExpandPress: { id: ExpandPanelIdentity } | null = null;
167
- let lastExpandClick: { id: ExpandPanelIdentity; at: number } | null = null;
168
- let armExpandClickTimer: ReturnType<typeof setTimeout> | null = null;
177
+ /** 单击判定:按下与松开落在同一格(renderer 合成 click 的条件),位移与时限各留容差。 */
178
+ const COLLAPSE_CLICK_MOVE_TOLERANCE = 1;
179
+ const COLLAPSE_CLICK_MAX_MS = 600;
180
+ /** 扩展收起自有卡后,官方 click 可能对同一次点击再 toggle 内部工具卡,短暂吞掉。 */
181
+ const SUPPRESS_OFFICIAL_CARD_CLICK_MS = 300;
182
+ /** 官方 fullscreen 的焦点事件串:发给原 handler 可清掉选区锚点(见 tui-alt-screen)。 */
183
+ const FULLSCREEN_FOCUS_OUT = "\x1b[O";
169
184
 
170
- function expandPanelIdentity(card: any): ExpandPanelIdentity {
171
- return card instanceof ThinkingPreviewBlock
172
- ? thinkingRunKey(card.messageTimestamp, card.runStartIndex)
173
- : card;
174
- }
185
+ let pendingCollapsePress: {
186
+ card: any;
187
+ /** 命中的最内层组件:官方工具卡由 MouseRegion 的 click 收起。 */
188
+ component: any;
189
+ col: number;
190
+ row: number;
191
+ at: number;
192
+ } | null = null;
193
+ let suppressOfficialCardClickUntil = 0;
175
194
 
176
- function isExpandPanelDoubleClick(card: any): boolean {
177
- const now = Date.now();
178
- const id = expandPanelIdentity(card);
179
- const prev = lastExpandClick;
180
- pendingExpandPress = { id };
181
- if (prev && prev.id === id && now - prev.at <= EXPAND_PANEL_DOUBLE_CLICK_MS) {
182
- pendingExpandPress = null;
183
- lastExpandClick = null;
184
- return true;
185
- }
186
- return false;
187
- }
188
-
189
- function completeExpandPanelClick(): void {
190
- if (!pendingExpandPress) return;
191
- const click = { id: pendingExpandPress.id, at: Date.now() };
192
- pendingExpandPress = null;
193
- if (armExpandClickTimer) clearTimeout(armExpandClickTimer);
194
- armExpandClickTimer = setTimeout(() => {
195
- armExpandClickTimer = null;
196
- if (pendingExpandPress) return;
197
- lastExpandClick = click;
198
- }, EXPAND_PANEL_CLICK_ARM_MS);
199
- if (
200
- typeof armExpandClickTimer === "object" &&
201
- armExpandClickTimer &&
202
- "unref" in armExpandClickTimer
203
- ) {
204
- armExpandClickTimer.unref();
205
- }
195
+ function clearPendingCollapsePress(): void {
196
+ pendingCollapsePress = null;
206
197
  }
207
198
 
208
- function clearExpandPanelDoubleClick(): void {
209
- pendingExpandPress = null;
210
- lastExpandClick = null;
211
- if (armExpandClickTimer) {
212
- clearTimeout(armExpandClickTimer);
213
- armExpandClickTimer = null;
199
+ /** 带键拖动或滚轮:位置真的变了(超出抖动容差)就不再是单击。 */
200
+ function clearPendingCollapsePressOnMove(packet: SgrMousePacket): void {
201
+ const press = pendingCollapsePress;
202
+ if (!press) return;
203
+ if ((packet.code & 64) !== 0) {
204
+ pendingCollapsePress = null;
205
+ return;
214
206
  }
207
+ if (Math.abs(packet.col - press.col) <= COLLAPSE_CLICK_MOVE_TOLERANCE) {
208
+ if (Math.abs(packet.row - press.row) <= COLLAPSE_CLICK_MOVE_TOLERANCE) return;
209
+ }
210
+ pendingCollapsePress = null;
215
211
  }
216
212
 
217
- function collapseExpandedCard(tui: any, card: any): boolean {
218
- // 单击也 consume,避免官方链再合成一次 press 把单击当成双击。
219
- if (!isExpandPanelDoubleClick(card)) return true;
220
- card.setExpanded(false);
213
+ function clearHoverState(): void {
221
214
  setHoveredToolCallId(null);
222
215
  setHoveredToolGroup(null);
223
216
  setHoveredThinking(null);
224
217
  setHoveredMessageDisplay(null);
225
218
  setHoveredToolIo(null, null);
226
219
  setHoveredCompactAssistant(null);
220
+ }
221
+
222
+ /** 收起一张展开卡(group 的 setExpanded 会传播到内部工具)。 */
223
+ function collapseExpandedCard(tui: any, card: any): void {
224
+ card.setExpanded(false);
225
+ clearHoverState();
227
226
  card.invalidate?.();
228
- tui.requestRender?.();
229
- clearExpandPanelDoubleClick();
230
- return true;
227
+ tui?.requestRender?.();
228
+ }
229
+
230
+ function rememberCollapsePress(card: any, component: any, packet: SgrMousePacket): void {
231
+ pendingCollapsePress = { card, component, col: packet.col, row: packet.row, at: Date.now() };
232
+ }
233
+
234
+ /**
235
+ * 松手结算:只有"按下与松开在同一格"的完整单击才收起,拖动选择文本时保持展开。
236
+ * 官方工具卡(result 区的 MouseRegion)由 guard 在官方 click 里收起,这里必须跳过,
237
+ * 否则先收起会被官方 click 的 setExpanded(!expanded) 翻回来。
238
+ */
239
+ function resolveCollapsePress(
240
+ tui: any,
241
+ packet: SgrMousePacket,
242
+ options: { skipOfficialCards: boolean },
243
+ ): any {
244
+ const press = pendingCollapsePress;
245
+ pendingCollapsePress = null;
246
+ if (!press || packet.final !== "m") return undefined;
247
+ if (Math.abs(packet.col - press.col) > COLLAPSE_CLICK_MOVE_TOLERANCE) return undefined;
248
+ if (Math.abs(packet.row - press.row) > COLLAPSE_CLICK_MOVE_TOLERANCE) return undefined;
249
+ if (Date.now() - press.at > COLLAPSE_CLICK_MAX_MS) return undefined;
250
+ if (options.skipOfficialCards && press.component instanceof ToolExecutionComponent)
251
+ return undefined;
252
+ const card = press.card;
253
+ if (
254
+ (card instanceof ToolGroupComponent || isCompactAssistantComponent(card)) &&
255
+ press.component !== card
256
+ ) {
257
+ // 点击落在卡内工具行:官方 click 会对同一次点击再次 toggle,吞掉它。
258
+ suppressOfficialCardClickUntil = Date.now() + SUPPRESS_OFFICIAL_CARD_CLICK_MS;
259
+ }
260
+ collapseExpandedCard(tui, card);
261
+ // 返回收起的卡:调用方只对 compact 面板清官方选区。
262
+ return card;
231
263
  }
232
264
 
233
265
  function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
@@ -237,12 +269,15 @@ function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
237
269
  if (region.kind === "show-more") return tryOpenToolIoShowMore(region);
238
270
  const component = region.component;
239
271
  if (!component) return false;
240
- if (region.kind === "expanded-card") return collapseExpandedCard(tui, component);
272
+ if (region.kind === "expanded-card" || region.kind === "collapse-hint") {
273
+ // regular 模式没有官方 click 合成,按下先记账,松手结算是否收起。
274
+ rememberCollapsePress(component, component, packet);
275
+ return true;
276
+ }
241
277
  component.setExpanded(true);
242
- clearExpandPanelDoubleClick();
278
+ clearPendingCollapsePress();
243
279
  setHoveredToolCallId(null);
244
280
  setHoveredToolGroup(null);
245
- setHoveredThinking(null);
246
281
  setHoveredToolIo(null, null);
247
282
  component.invalidate?.();
248
283
  tui.requestRender?.();
@@ -312,11 +347,37 @@ function releaseFullscreenToolMouseMotion(tui?: any): void {
312
347
  const FULLSCREEN_VIEWPORT_PATCH = Symbol("ccstyle.fullscreen-viewport-patch");
313
348
  const FULLSCREEN_WHEEL_SCROLL_ORIGINAL = Symbol("ccstyle.fullscreen-wheel-scroll-original");
314
349
 
350
+ /**
351
+ * diff 结果组件自带 remainder 行:声明存在时只有那一行是展开入口(正文里的同名字样不算);
352
+ * 未声明的组件继续用文本规则。
353
+ */
354
+ function isCollapsedHintRow(component: any, finalLine: string): boolean {
355
+ const result = component?.resultRendererComponent;
356
+ return typeof result?.isCollapsedHintLine === "function"
357
+ ? result.isCollapsedHintLine(finalLine)
358
+ : true;
359
+ }
360
+
361
+ /**
362
+ * pi 0.87 给工具结果区套了 MouseRegion:整卡左键 click 都会 setExpanded。
363
+ * 命中该区域时官方会跳过文本选区,所以可以安全吞掉;单行摘要卡没有声明
364
+ * remainder 行,继续走官方整行行为。
365
+ */
366
+ function blocksOfficialCardToggle(component: any, event: any): boolean {
367
+ if (event?.type !== "click" || event.button !== "left" || component?.expanded) return false;
368
+ const result = component?.resultRendererComponent;
369
+ if (typeof result?.isCollapsedHintLine !== "function") return false;
370
+ const lines = component.render?.(Math.max(1, Math.floor(Number(event.width) || 0)));
371
+ const line = Array.isArray(lines) ? lines[event.y] : undefined;
372
+ return typeof line === "string" && !result.isCollapsedHintLine(line);
373
+ }
374
+
315
375
  /**
316
376
  * 官方 fullscreen 工具卡点击:collapsed hint 单击展开
317
- * (有且仅保持一个展开:展开前收起其他工具卡),expanded 整卡双击收起,
318
- * 截断头 show-more 单击打开全量预览;回到底部按钮 scrollToBottom。
319
- * 滚动条列、含 OSC8 链接行、非工具区域、展开卡单击放行官方。
377
+ * (有且仅保持一个展开:展开前收起其他工具卡),expanded 卡放行官方 press,
378
+ * 位置不变的松手才收起(官方卡经 guard,思考/group/compact 自有卡由扩展结算),
379
+ * 拖动选择文本时不收起;截断头 show-more 单击打开全量预览;回到底部按钮 scrollToBottom。
380
+ * 滚动条列、含 OSC8 链接行、非工具区域、折叠卡正文放行官方。
320
381
  */
321
382
  function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
322
383
  const layout = tui.currentLayout;
@@ -334,7 +395,10 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
334
395
  const target = componentAtLocalRow(hit.box.component, hit.localRow, contentWidth);
335
396
  if (!target) return false;
336
397
  const component = target.component;
337
- const card = target.group ?? component;
398
+ // compact 展开面板内命中时 owner 是面板:收起对象与记账对象都归面板,
399
+ // 否则 skipOfficialCards 会把内部工具当成官方卡跳过。
400
+ const card = target.group ?? target.owner ?? component;
401
+ const pressComponent = target.owner ?? component;
338
402
  // 回到底部按钮:按组件引用命中,不依赖渲染行缓存。
339
403
  if (getScrollButtonVisible() && component === getScrollButtonWidget()) {
340
404
  tui.scrollToBottom?.();
@@ -352,20 +416,32 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
352
416
  if (!component.expanded) {
353
417
  // collapsed 仅按钮文本可展开,不能把同一行正文/留白变成点击区。
354
418
  const hint = collapsedHintHitbox(line);
355
- if (!hint || packet.col < hint.startCol || packet.col > hint.endCol) return false;
419
+ const onHint = Boolean(
420
+ hint &&
421
+ packet.col >= hint.startCol &&
422
+ packet.col <= hint.endCol &&
423
+ isCollapsedHintRow(component, line),
424
+ );
425
+ if (!onHint) {
426
+ // 面板内非提示区(工具卡标题/摘要行、thinking 预览正文):单击收起整块面板。
427
+ if (target.owner) rememberCollapsePress(target.owner, target.owner, packet);
428
+ return false;
429
+ }
356
430
  // single-expand:展开前收起其他已展开工具卡/group。
357
431
  const others: any[] = [];
358
432
  collectFullscreenToolCards(hit.box.component, others);
359
433
  for (const other of others) {
360
434
  if (other !== component && other.expanded) {
361
- // 展开 round 内 thinking 时不要把外层 compact 卡收起。
362
- if (isThinking && isCompactAssistantComponent(other)) continue;
435
+ // 展开 round 卡内 thinking/工具时,外层 compact 卡是它的容器,不能收起。
436
+ if ((isThinking || isTool) && isCompactAssistantComponent(other)) continue;
363
437
  other.setExpanded(false);
364
438
  other.invalidate?.();
365
439
  }
366
440
  }
441
+ // 展开 round 卡内工具时,让 compact 的强制折叠放行它(非 round 内工具为空操作)。
442
+ if (isTool) markCompactRoundToolExpanded(component);
367
443
  component.setExpanded(true);
368
- clearExpandPanelDoubleClick();
444
+ clearPendingCollapsePress();
369
445
  } else {
370
446
  // 普通工具截断头 show-more:打开全量预览(不收起)。
371
447
  const view = isTool ? component.resultRendererComponent : null;
@@ -373,7 +449,7 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
373
449
  const plain = stripTerminalSequencesPreservingLayout(line);
374
450
  const section = view.matchShowMoreLine(plain);
375
451
  if (section) {
376
- clearExpandPanelDoubleClick();
452
+ clearPendingCollapsePress();
377
453
  const box = view.showMoreHitbox(plain);
378
454
  return tryOpenToolIoShowMore({
379
455
  kind: "show-more",
@@ -386,17 +462,20 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
386
462
  });
387
463
  }
388
464
  }
389
- // 双击收起:内部工具仍归所属 group。单击放行官方(选择/链接)。
390
- return collapseExpandedCard(tui, card);
465
+ // 其余放行官方:选区、OSC8 链接与 click 合成都交给 renderer。
466
+ // 完整单击的收起:官方卡由 guard 处理,自有卡在松手时结算。
467
+ rememberCollapsePress(card, pressComponent, packet);
468
+ return false;
391
469
  }
392
- // 点击后清 hover 高亮。
470
+ // 点击后清 hover 高亮。只 invalidate 点中的组件(或它所在的 group),
471
+ // 不要动 owner 面板:面板 invalidate 会连带重跑 updateContent,重建 thinking 实例。
393
472
  setHoveredToolCallId(null);
394
473
  setHoveredToolGroup(null);
395
474
  setHoveredThinking(null);
396
475
  setHoveredMessageDisplay(null);
397
476
  setHoveredToolIo(null, null);
398
477
  setHoveredCompactAssistant(null);
399
- card.invalidate?.();
478
+ (target.group ?? component).invalidate?.();
400
479
  tui.requestRender?.();
401
480
  return true;
402
481
  }
@@ -406,7 +485,7 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
406
485
  * expanded 卡截断头 show-more、回到底部按钮。motion 不 consume,官方链照常。
407
486
  */
408
487
  function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
409
- if (packet.final !== "M") return;
488
+ if (!isSgrIdleMotion(packet)) return;
410
489
  const layout = tui.currentLayout;
411
490
  if (!layout?.root) return;
412
491
  const x = packet.col - 1;
@@ -500,17 +579,33 @@ function patchFullscreenViewportInput(tui: any): void {
500
579
  if (packets && tui.hasOverlay?.() && hasActiveTextPreview()) return undefined;
501
580
  if (packets && !tui.hasOverlay?.()) {
502
581
  for (const packet of packets) {
503
- if (isSgrLeftRelease(packet)) completeExpandPanelClick();
582
+ if (isSgrLeftRelease(packet)) {
583
+ // 只给 compact 面板清选区:自有面板收起会重排布局,官方选区锚点
584
+ // 还停在旧布局上,同一次 release 会结算出多行高亮。
585
+ if (
586
+ isCompactAssistantComponent(
587
+ resolveCollapsePress(tui, packet, { skipOfficialCards: true }),
588
+ )
589
+ ) {
590
+ try {
591
+ Reflect.apply(original, this, [FULLSCREEN_FOCUS_OUT]);
592
+ } catch {
593
+ /* 老版本没有选区状态 */
594
+ }
595
+ }
596
+ } else if (!isSgrLeftPress(packet) && !isSgrIdleMotion(packet))
597
+ clearPendingCollapsePressOnMove(packet);
504
598
  if (isSgrLeftPress(packet) && handleFullscreenToolClick(tui, packet)) {
505
599
  return { consume: true };
506
600
  }
507
- if ((packet.code & 32) !== 0 && packet.final === "M") {
601
+ // 仅无按键移动走 hover;左键拖动(文本多选)放行官方选区,避免每像素命中+重绘。
602
+ if (isSgrIdleMotion(packet)) {
508
603
  handleFullscreenToolHover(tui, packet);
509
604
  }
510
605
  }
511
606
  }
512
607
  }
513
- return original.apply(this, [data]);
608
+ return Reflect.apply(original, this, [data]);
514
609
  };
515
610
  }
516
611
 
@@ -574,7 +669,11 @@ function buildInteractionFrame(
574
669
  const line = placement.finalLine;
575
670
  if (!component.expanded) {
576
671
  const box = collapsedHintHitbox(line);
577
- if (box && COLLAPSED_TOOL_SUMMARY.test(stripTerminalSequences(line))) {
672
+ if (
673
+ box &&
674
+ COLLAPSED_TOOL_SUMMARY.test(stripTerminalSequences(line)) &&
675
+ isCollapsedHintRow(component, line)
676
+ ) {
578
677
  regions.push({ kind: "collapsed-hint", row: finalRow, ...box, component });
579
678
  }
580
679
  continue;
@@ -608,6 +707,11 @@ function buildInteractionFrame(
608
707
  if (placement.componentRow < cardStart) continue;
609
708
  const finalRow = lineIndexToScreenRow(placement.lineIndex);
610
709
  if (finalRow >= 1 && finalRow <= visibleRows) {
710
+ // 展开态的 ↑ Collapse 提示单独成区:hover 高亮 hint,点击仍收起整卡。
711
+ const hintBox = collapsedHintHitbox(placement.finalLine);
712
+ if (hintBox && isCollapseHintLine(stripTerminalSequences(placement.finalLine))) {
713
+ regions.push({ kind: "collapse-hint", row: finalRow, ...hintBox, component });
714
+ }
611
715
  regions.push({
612
716
  kind: "expanded-card",
613
717
  row: finalRow,
@@ -650,7 +754,7 @@ function wrapToolRendersForFrame(
650
754
  ) {
651
755
  const boxOriginal = box.render;
652
756
  const boxWrapped = function (this: any, ...boxArgs: any[]) {
653
- const boxLines = boxOriginal.apply(this, boxArgs);
757
+ const boxLines = Reflect.apply(boxOriginal, this, boxArgs);
654
758
  if (Array.isArray(boxLines)) contentBoxLines = boxLines.length;
655
759
  return boxLines;
656
760
  };
@@ -660,7 +764,7 @@ function wrapToolRendersForFrame(
660
764
  }
661
765
  }
662
766
  try {
663
- const lines = originalRender.apply(this, renderArgs);
767
+ const lines = Reflect.apply(originalRender, this, renderArgs);
664
768
  if (!Array.isArray(lines)) return lines;
665
769
  renderedTools.push({
666
770
  component,
@@ -736,10 +840,10 @@ function patchToolMouseMotionAfterRender(tui: any): void {
736
840
  const rawWrite = typeof terminal?.write === "function" ? terminal.write : undefined;
737
841
  if (typeof original !== "function") return;
738
842
 
739
- toolMouseRawWrite = rawWrite ? (data) => rawWrite.apply(terminal, [data]) : null;
843
+ toolMouseRawWrite = rawWrite ? (data) => Reflect.apply(rawWrite, terminal, [data]) : null;
740
844
  const patchState = { active: true };
741
845
  const wrapper = function (this: any, ...args: any[]) {
742
- if (!patchState.active) return original.apply(this, args);
846
+ if (!patchState.active) return Reflect.apply(original, this, args);
743
847
  const renderedTools: FrameToolRender[] = [];
744
848
  const idToComponent = new Map<number, any>();
745
849
  const frame: IoViewFrameState = {
@@ -756,7 +860,7 @@ function patchToolMouseMotionAfterRender(tui: any): void {
756
860
  let sawTuiRender = false;
757
861
  if (originalTuiRender) {
758
862
  const wrappedTuiRender = function (this: any, ...renderArgs: any[]) {
759
- const lines = originalTuiRender.apply(this, renderArgs);
863
+ const lines = Reflect.apply(originalTuiRender, this, renderArgs);
760
864
  if (!Array.isArray(lines)) return lines;
761
865
  sawTuiRender = true;
762
866
  const extracted = extractToolFramePlacements(
@@ -773,7 +877,7 @@ function patchToolMouseMotionAfterRender(tui: any): void {
773
877
  const previousFrame = getActiveIoViewFrame();
774
878
  setActiveIoViewFrame(frame);
775
879
  try {
776
- const result = original.apply(this, args);
880
+ const result = Reflect.apply(original, this, args);
777
881
  succeeded = true;
778
882
  // Test harnesses may paint via doRender without tui.render; recover markers there.
779
883
  if (!sawTuiRender && Array.isArray(this.previousLines)) {
@@ -837,8 +941,15 @@ function handleToolMouseInput(data: string): { consume: true } | undefined {
837
941
  let consumed = false;
838
942
  for (const packet of packets) {
839
943
  updateToolSummaryHover(getToolMouseTui(), packet);
840
- if (isSgrLeftRelease(packet)) completeExpandPanelClick();
841
- if (!isSgrLeftPress(packet)) continue;
944
+ if (isSgrLeftRelease(packet)) {
945
+ resolveCollapsePress(getToolMouseTui(), packet, { skipOfficialCards: false });
946
+ continue;
947
+ }
948
+ if (!isSgrLeftPress(packet)) {
949
+ // 带键拖动或滚轮:位置变了就不再是单击。
950
+ if (!isSgrIdleMotion(packet)) clearPendingCollapsePressOnMove(packet);
951
+ continue;
952
+ }
842
953
  if (toggleToolAtMouseClick(getToolMouseTui(), packet)) {
843
954
  consumed = true;
844
955
  }
@@ -851,10 +962,10 @@ function handleToolMouseInput(data: string): { consume: true } | undefined {
851
962
  }
852
963
 
853
964
  export function teardownToolMouseInteraction(
854
- owner: ToolMouseOwner = toolMouseInstallationOwner ?? DEFAULT_TOOL_MOUSE_OWNER,
965
+ owner: object = toolMouseInstallationOwner ?? DEFAULT_TOOL_MOUSE_OWNER,
855
966
  ): void {
856
- const host = globalThis as any;
857
- if (host[TOOL_MOUSE_OWNER_KEY] && host[TOOL_MOUSE_OWNER_KEY] !== owner) return;
967
+ const current = patchRegistry.get<object>(TOOL_MOUSE_OWNER_KEY);
968
+ if (current && current !== owner) return;
858
969
  if (sessionRenderTimer) {
859
970
  clearTimeout(sessionRenderTimer);
860
971
  sessionRenderTimer = null;
@@ -867,7 +978,9 @@ export function teardownToolMouseInteraction(
867
978
  setHoveredMessageDisplay(null);
868
979
  setHoveredToolIo(null, null);
869
980
  setHoveredCompactAssistant(null);
870
- clearExpandPanelDoubleClick();
981
+ releaseToolCardMouseGuard();
982
+ clearPendingCollapsePress();
983
+ suppressOfficialCardClickUntil = 0;
871
984
  try {
872
985
  if (isLazyProxyTui(getToolMouseTui())) releaseFullscreenToolMouseMotion(getToolMouseTui());
873
986
  else getToolMouseTui()?.terminal?.write?.(TOOL_MOUSE_DISABLE);
@@ -885,7 +998,7 @@ export function teardownToolMouseInteraction(
885
998
  resetScrollButtonState();
886
999
  setToolMouseTui(null);
887
1000
  toolMouseUi = null;
888
- if (host[TOOL_MOUSE_OWNER_KEY] === owner) delete host[TOOL_MOUSE_OWNER_KEY];
1001
+ patchRegistry.dispose(TOOL_MOUSE_OWNER_KEY, owner);
889
1002
  if (toolMouseInstallationOwner === owner) toolMouseInstallationOwner = null;
890
1003
  }
891
1004
 
@@ -901,9 +1014,66 @@ export function resetToolHoverState(): void {
901
1014
  releaseFullscreenToolMouseMotion(getToolMouseTui());
902
1015
  }
903
1016
 
1017
+ /**
1018
+ * pi 0.87 起工具卡结果区自带 MouseRegion,整卡左键 click 都会 setExpanded。
1019
+ * ccstyle 的多行折叠卡(rich diff)只允许声明的 remainder 行展开,
1020
+ * 其余位置直接吞掉 click,避免官方把 diff 正文当展开入口。
1021
+ * 0.84 及更早没有 handleMouse,安装时跳过。
1022
+ */
1023
+ const TOOL_CARD_MOUSE_GUARD_KEY = Symbol.for("pi.ccstyle.tool-card-mouse-guard");
1024
+
1025
+ type ToolCardMouseGuard = {
1026
+ prototype: any;
1027
+ original: (event: any) => any;
1028
+ wrapper: (event: any) => any;
1029
+ };
1030
+
1031
+ /** 官方 click 落在展开卡上:同一 group 的任意内部工具行都收起整个 group。 */
1032
+ function collapseExpandedCardFromOfficialClick(component: any): void {
1033
+ if (typeof component?.setExpanded !== "function") return;
1034
+ const group = component?.[TOOL_GROUPING_PARENT_KEY];
1035
+ collapseExpandedCard(getToolMouseTui(), group instanceof ToolGroupComponent ? group : component);
1036
+ }
1037
+
1038
+ function installToolCardMouseGuard(): void {
1039
+ const prototype = (ToolExecutionComponent as any)?.prototype;
1040
+ if (typeof prototype?.handleMouse !== "function") return;
1041
+ const previous = (globalThis as any)[TOOL_CARD_MOUSE_GUARD_KEY] as ToolCardMouseGuard | undefined;
1042
+ if (previous && previous.prototype === prototype && prototype.handleMouse === previous.wrapper)
1043
+ return;
1044
+ // 每次取当前值作为下游,可能是别的扩展的包装。
1045
+ const original = prototype.handleMouse;
1046
+ const wrapper = function (this: any, event: any) {
1047
+ if (event?.type === "click" && event.button === "left") {
1048
+ // 扩展刚收起过自有卡:这次 click 属于同一次单击,吞掉避免内部工具被翻回展开。
1049
+ if (suppressOfficialCardClickUntil && Date.now() <= suppressOfficialCardClickUntil) {
1050
+ suppressOfficialCardClickUntil = 0;
1051
+ if (this?.expanded) collapseExpandedCardFromOfficialClick(this);
1052
+ return { handled: true };
1053
+ }
1054
+ // 展开卡整卡 click:接管官方 toggle,改为收起(group 内部工具行收起整个 group)。
1055
+ if (this?.expanded && typeof this.setExpanded === "function") {
1056
+ collapseExpandedCardFromOfficialClick(this);
1057
+ return { handled: true };
1058
+ }
1059
+ }
1060
+ if (blocksOfficialCardToggle(this, event)) return { handled: true };
1061
+ return original.call(this, event);
1062
+ };
1063
+ prototype.handleMouse = wrapper;
1064
+ (globalThis as any)[TOOL_CARD_MOUSE_GUARD_KEY] = { prototype, original, wrapper };
1065
+ }
1066
+
1067
+ function releaseToolCardMouseGuard(): void {
1068
+ const guard = (globalThis as any)[TOOL_CARD_MOUSE_GUARD_KEY] as ToolCardMouseGuard | undefined;
1069
+ if (!guard) return;
1070
+ if (guard.prototype?.handleMouse === guard.wrapper) guard.prototype.handleMouse = guard.original;
1071
+ (globalThis as any)[TOOL_CARD_MOUSE_GUARD_KEY] = undefined;
1072
+ }
1073
+
904
1074
  export function installToolMouseInteraction(
905
1075
  ctx: any,
906
- owner: ToolMouseOwner = DEFAULT_TOOL_MOUSE_OWNER,
1076
+ owner: object = DEFAULT_TOOL_MOUSE_OWNER,
907
1077
  ): void {
908
1078
  teardownToolMouseInteraction(toolMouseInstallationOwner ?? owner);
909
1079
  if (ctx?.mode !== "tui" || !ctx?.hasUI) return;
@@ -911,7 +1081,8 @@ export function installToolMouseInteraction(
911
1081
  return;
912
1082
 
913
1083
  toolMouseInstallationOwner = owner;
914
- (globalThis as any)[TOOL_MOUSE_OWNER_KEY] = owner;
1084
+ patchRegistry.install(TOOL_MOUSE_OWNER_KEY, owner);
1085
+ installToolCardMouseGuard();
915
1086
  setHoveredToolCallId(null);
916
1087
  toolMouseUi = ctx.ui;
917
1088
  // 0.84+ 的 tui 是惰性 Proxy:regular 保留原生 scrollback;fullscreen
@@ -920,14 +1091,14 @@ export function installToolMouseInteraction(
920
1091
  setToolMouseTui(tui);
921
1092
  setToolTuiFullscreen(fullscreenLazyTui(tui));
922
1093
  if (isLazyProxyTui(tui)) {
923
- syncOfficialScrollToEnd(tui);
924
1094
  patchFullscreenViewportInput(tui);
925
1095
  ensureFullscreenToolMouseMotion(tui);
1096
+ syncOfficialScrollToEnd(tui);
926
1097
  setScrollButtonWidget({
927
1098
  render: (width: number) => {
928
- syncOfficialScrollToEnd(tui);
929
1099
  patchFullscreenViewportInput(tui);
930
1100
  ensureFullscreenToolMouseMotion(tui);
1101
+ syncOfficialScrollToEnd(tui);
931
1102
  return renderScrollButton(width, theme);
932
1103
  },
933
1104
  invalidate() {},
@@ -969,10 +1140,3 @@ export function scheduleSessionRender(refresh?: () => void): void {
969
1140
  tui.requestRender(true);
970
1141
  }, 0);
971
1142
  }
972
-
973
- export {
974
- hoveredToolCallId,
975
- isToolCallHovered,
976
- setHoveredToolGroup,
977
- setHoveredToolIo,
978
- } from "./hover.ts";