@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,6 +1,6 @@
1
1
  import { normalizeDiffRenderWidth } from "./diff-presentation.ts";
2
- import { pluralize } from "./render-utils.ts";
3
- import { showMoreHintText } from "../../show-more-hint.ts";
2
+ import { pluralize } from "./diff-text.ts";
3
+ import { showMoreHintText } from "../show-more-hint.ts";
4
4
 
5
5
  export interface WidthMeasurementOps {
6
6
  measure(text: string): number;
@@ -94,6 +94,7 @@ export async function executeWriteWithMetadata(
94
94
  await mkdir(dirname(absolutePath), { recursive: true });
95
95
  throwIfAborted();
96
96
  await writeFile(absolutePath, params.content, "utf8");
97
+ // 写入后不再检查 abort:文件已变更,必须如实报告成功
97
98
  store.set(toolCallId, metadata);
98
99
  return {
99
100
  content: [
@@ -7,13 +7,26 @@ import {
7
7
  type Component,
8
8
  } from "@earendil-works/pi-tui";
9
9
  import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "../../utils/tool-loading-icon.ts";
10
- import { sanitizeToolResultText } from "../../utils/tool-result-sanitize.ts";
11
- import { fitToolCallSummary, pathSummary, type ToolCallSummary } from "./result.ts";
12
- import { isToolTuiFullscreen, showMoreHintText } from "../show-more-hint.ts";
10
+ import { getToolMouseTui } from "../mouse/scroll.ts";
11
+ import { mcpToolTitle } from "./mcp-title.ts";
12
+ import { collapseHintText, isToolTuiFullscreen, showMoreHintText } from "./show-more-hint.ts";
13
+ import { stripAnsi, stripBackgroundAnsi, stripLeadingStatusIcon } from "../../utils/ansi-text.ts";
14
+ import { walkComponentTree } from "../../utils/component-tree.ts";
15
+ import {
16
+ fitToolCallSummary,
17
+ humanizeToolLabel,
18
+ renderToolSummary,
19
+ toolCallSummary,
20
+ type ToolCallSummary,
21
+ } from "./names.ts";
22
+ import { toolViewportWidth } from "./result.ts";
23
+ import {
24
+ patchRegistry,
25
+ TOOL_GROUPING_GENERATION_KEY as GENERATION_KEY,
26
+ TOOL_GROUPING_PARENT_KEY as PARENT_KEY,
27
+ TOOL_GROUPING_PATCH_KEY as PATCH_KEY,
28
+ } from "../../utils/patch-keys.ts";
13
29
 
14
- const PATCH_KEY = Symbol.for("pi.ccstyle.tool-grouping-patch");
15
- const PARENT_KEY = Symbol.for("pi.ccstyle.tool-grouping-parent");
16
- const GENERATION_KEY = Symbol.for("pi.ccstyle.tool-grouping-generation");
17
30
  const NON_GROUPABLE = new Set(["edit", "write", "apply_patch"]);
18
31
 
19
32
  type Patch = {
@@ -34,7 +47,20 @@ function toolName(tool: any): string {
34
47
  return String(tool?.toolName ?? tool?.toolDefinition?.name ?? "tool");
35
48
  }
36
49
 
37
- function isGroupable(value: unknown): value is any {
50
+ /**
51
+ * 标题:MCP 工具用 adapter 暴露的真实工具名,其余回退工具名人性化。
52
+ */
53
+ function toolTitle(tool: any): string {
54
+ const name = toolName(tool);
55
+ return (
56
+ mcpToolTitle({
57
+ toolName: name,
58
+ definition: tool?.toolDefinition ?? tool?.builtInToolDefinition,
59
+ }) ?? humanizeToolLabel(name)
60
+ );
61
+ }
62
+
63
+ function isGroupable(value: unknown): boolean {
38
64
  return value instanceof ToolExecutionComponent && !NON_GROUPABLE.has(toolName(value));
39
65
  }
40
66
 
@@ -80,37 +106,42 @@ function scheduleGroupAnimation(patch: Patch): void {
80
106
  patch.animationTimer = setTimeout(() => {
81
107
  patch.animationTimer = null;
82
108
  if (!patch.active) return;
109
+ let needsRender = false;
83
110
  for (const group of patch.groups) {
84
111
  if (
85
112
  (group.children as any[]).some(
86
113
  (tool) => tool?.executionStarted && status(tool) === "pending",
87
114
  )
88
- )
115
+ ) {
89
116
  group.invalidate();
117
+ // 分组卡不是 ToolExecutionComponent:它的 invalidate() 不会请求渲染,
118
+ // 不补这一步 spinner 只在别人渲染时才跳帧(并行工具下表现为卡顿/冻结)。
119
+ needsRender = true;
120
+ }
90
121
  }
122
+ if (needsRender) requestAnimationRender(patch);
91
123
  }, TOOL_LOADING_INTERVAL_MS);
92
124
  patch.animationTimer.unref?.();
93
125
  }
94
126
 
95
- function stripAnsi(line: string): string {
96
- return line.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
127
+ /** 借用子工具卡的 ui 请求一帧;子卡缺失时回退到扩展持有的 TUI 槽。 */
128
+ function requestAnimationRender(patch: Patch): void {
129
+ for (const group of patch.groups) {
130
+ const ui = (group.children as any[]).find(
131
+ (tool) => typeof tool?.ui?.requestRender === "function",
132
+ )?.ui;
133
+ if (ui) {
134
+ ui.requestRender();
135
+ return;
136
+ }
137
+ }
138
+ getToolMouseTui()?.requestRender?.();
97
139
  }
98
140
 
99
141
  function visibleLines(lines: string[]): string[] {
100
142
  return lines.filter((line) => stripAnsi(line).trim());
101
143
  }
102
144
 
103
- function stripLeadingStatusIcon(line: string): string {
104
- return line.replace(
105
- /^((?:\x1b\[[0-9;]*m|[ \t]|[├└│─])*)(?:\x1b\[[0-9;]*m)*(?:[✓✗●○■⬤•·⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏])(?:\x1b\[[0-9;]*m)*\s+/,
106
- "$1",
107
- );
108
- }
109
-
110
- export function stripBackgroundAnsi(line: string): string {
111
- return line.replace(/\x1b\[(?:4[0-9]|10[0-7]|48(?:(?:;|:)[0-9]+)+|49)m/g, "");
112
- }
113
-
114
145
  function stripLeadingSpaces(line: string, count: number): string {
115
146
  let offset = 0;
116
147
  let removed = 0;
@@ -133,10 +164,6 @@ function stripLeadingSpaces(line: string, count: number): string {
133
164
  }
134
165
 
135
166
  /** 生成一行铺满 width 的 slot 背景行;bgAnsiOverride 可替换背景 ANSI(用于提亮等)。 */
136
- function panelInnerWidth(width: number): number {
137
- return Math.max(0, Math.floor(width) - 2);
138
- }
139
-
140
167
  export function paddedBackgroundRow(
141
168
  theme: any,
142
169
  slot: string,
@@ -144,12 +171,9 @@ export function paddedBackgroundRow(
144
171
  width: number,
145
172
  bgAnsiOverride?: string,
146
173
  ): string {
147
- const safeWidth = Math.max(0, Math.floor(width));
148
- const leftPadding = safeWidth > 0 ? " " : "";
149
- const rightPadding = safeWidth > 1 ? " " : "";
150
- const innerWidth = panelInnerWidth(width);
174
+ const innerWidth = Math.max(0, width - 2);
151
175
  const clipped = truncateToWidth(stripBackgroundAnsi(content), innerWidth, "");
152
- const row = `${leftPadding}${clipped}${" ".repeat(Math.max(0, innerWidth - visibleWidth(clipped)))}${rightPadding}`;
176
+ const row = ` ${clipped}${" ".repeat(Math.max(0, innerWidth - visibleWidth(clipped)))} `;
153
177
  const bgAnsi =
154
178
  bgAnsiOverride ||
155
179
  (typeof theme?.bg === "function"
@@ -160,113 +184,12 @@ export function paddedBackgroundRow(
160
184
  return `${bgAnsi}${stable}\x1b[49m`;
161
185
  }
162
186
 
163
- function oneLine(value: unknown): string {
164
- return sanitizeToolResultText(String(value ?? ""), 4096)
165
- .replace(/\s+/g, " ")
166
- .trim();
167
- }
168
-
169
- function humanizeToolName(name: string): string {
170
- return name
171
- .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
172
- .replace(/[_-]+/g, " ")
173
- .replace(/\b\w/g, (char) => char.toUpperCase());
174
- }
175
-
176
187
  function toolSummary(tool: any): ToolCallSummary {
177
- const name = toolName(tool);
178
- const lowerName = name.toLowerCase();
179
- const args = tool?.args ?? {};
180
- const titled = humanizeToolName(name);
181
- const value = (fallback: string, ...keys: string[]) => {
182
- const found = keys.map((key) => args[key]).find((item) => typeof item === "string" && item);
183
- return `${titled} ${oneLine(found || fallback)}`;
184
- };
185
- if (lowerName === "agent" || lowerName === "agents") {
186
- const displayName = args.subagent_type ?? args.agent_type ?? args.agent;
187
- if (typeof displayName === "string" && displayName) {
188
- return { main: `${titled} ${displayName}`, detail: "" };
189
- }
190
- return {
191
- main: value(
192
- lowerName === "agent" ? "launch agent" : "launch agents",
193
- "description",
194
- "prompt",
195
- ),
196
- detail: "",
197
- };
198
- }
199
- if (lowerName === "get_subagent_result" || lowerName === "steer_subagent") {
200
- return {
201
- main: value(lowerName === "get_subagent_result" ? "agent result" : "steer agent", "agent_id"),
202
- detail: "",
203
- };
204
- }
205
- if (lowerName === "skill") return { main: value("run skill", "name"), detail: "" };
206
- if (lowerName === "enterplanmode" || lowerName === "enter_plan_mode") {
207
- return { main: `${titled} enable read-only planning`, detail: "" };
208
- }
209
- if (lowerName === "exitplanmode" || lowerName === "exit_plan_mode") {
210
- return { main: `${titled} present plan`, detail: "" };
211
- }
212
- if (lowerName === "taskcreate") return { main: value("create task", "subject"), detail: "" };
213
- if (lowerName === "tasklist") return { main: `${titled} task list`, detail: "" };
214
- if (lowerName === "taskget" || lowerName === "taskupdate") {
215
- return { main: value("task", "taskId", "task_id"), detail: "" };
216
- }
217
- if (lowerName === "taskoutput" || lowerName === "taskstop") {
218
- return { main: value("background task", "task_id", "taskId"), detail: "" };
219
- }
220
- if (lowerName === "taskexecute") {
221
- const ids = Array.isArray(args.task_ids)
222
- ? args.task_ids
223
- : Array.isArray(args.taskIds)
224
- ? args.taskIds
225
- : [];
226
- return {
227
- main: `${titled} ${ids.length ? `${ids[0]}${ids.length > 1 ? ` (+${ids.length - 1} tasks)` : ""}` : "start tasks"}`,
228
- detail: "",
229
- };
230
- }
231
- if (name === "read") {
232
- const details = [
233
- args.offset !== undefined ? `offset=${args.offset}` : "",
234
- args.limit !== undefined ? `limit=${args.limit}` : "",
235
- ].filter(Boolean);
236
- const detail = details.length ? ` (${details.join(", ")})` : "";
237
- if (typeof args.path === "string" && args.path) {
238
- return pathSummary("Read", args.path, tool?.cwd, detail);
239
- }
240
- return { main: "Read ...", detail };
241
- }
242
- if (name === "bash") return { main: `Bash ${oneLine(args.command || "...")}`, detail: "" };
243
- if (name === "grep") {
244
- const pattern = oneLine(args.pattern || "...");
245
- return {
246
- main: `Grep ${JSON.stringify(pattern)}${args.path ? ` in ${oneLine(args.path)}` : ""}`,
247
- detail: "",
248
- };
249
- }
250
- if (name === "find") {
251
- const pattern = oneLine(args.pattern || "...");
252
- return {
253
- main: `Find ${JSON.stringify(pattern)}${args.path ? ` in ${oneLine(args.path)}` : ""}`,
254
- detail: "",
255
- };
256
- }
257
- const preferred =
258
- args.agent_id ??
259
- args.path ??
260
- args.file_path ??
261
- args.url ??
262
- args.description ??
263
- args.query ??
264
- args.name ??
265
- args.prompt;
266
- return {
267
- main: `${humanizeToolName(name)}${preferred === undefined ? "" : ` ${oneLine(preferred)}`}`,
268
- detail: "",
269
- };
188
+ return toolCallSummary(toolName(tool), tool?.args ?? {}, {
189
+ title: toolTitle(tool),
190
+ variant: "grouping",
191
+ cwd: tool?.cwd,
192
+ });
270
193
  }
271
194
 
272
195
  function toolNameList(tools: any[]): string {
@@ -277,10 +200,6 @@ function toolNameList(tools: any[]): string {
277
200
 
278
201
  let nextGroupId = 1;
279
202
 
280
- function groupChildWidth(width: number): number {
281
- return Math.max(1, panelInnerWidth(width));
282
- }
283
-
284
203
  type SettledGroupCache = {
285
204
  width: number;
286
205
  hover: boolean;
@@ -292,6 +211,15 @@ type SettledGroupCache = {
292
211
  lines: string[];
293
212
  };
294
213
 
214
+ type ExpandedGroupCache = {
215
+ width: number;
216
+ hover: boolean;
217
+ theme: unknown;
218
+ fullscreen: boolean;
219
+ paints: readonly unknown[];
220
+ lines: string[];
221
+ };
222
+
295
223
  export class ToolGroupComponent extends Container {
296
224
  readonly toolCallId = `ccstyle-tool-group-${nextGroupId++}`;
297
225
  readonly toolName = "Tool group";
@@ -302,8 +230,10 @@ export class ToolGroupComponent extends Container {
302
230
  }
303
231
  private hintHovered = false;
304
232
  private readonly patch: Patch;
305
- /** 仅缓存已完成且折叠的分组;pending / expanded 每帧现算。 */
233
+ /** 仅缓存已完成且折叠的分组;pending 每帧现算。 */
306
234
  private settledCache: SettledGroupCache | undefined;
235
+ /** 展开分组:子工具 paint 引用未变则复用整卡行。 */
236
+ private expandedPaintCache: ExpandedGroupCache | undefined;
307
237
 
308
238
  constructor(patch: Patch) {
309
239
  super();
@@ -311,14 +241,19 @@ export class ToolGroupComponent extends Container {
311
241
  patch.groups.add(this);
312
242
  }
313
243
 
314
- addTool(tool: any): void {
244
+ private clearPaintCache(): void {
315
245
  this.settledCache = undefined;
246
+ this.expandedPaintCache = undefined;
247
+ }
248
+
249
+ addTool(tool: any): void {
250
+ this.clearPaintCache();
316
251
  this.children.push(tool);
317
252
  tool[PARENT_KEY] = this;
318
253
  }
319
254
 
320
255
  releaseTools(): any[] {
321
- this.settledCache = undefined;
256
+ this.clearPaintCache();
322
257
  const tools = [...this.children];
323
258
  this.children.length = 0;
324
259
  this.patch.groups.delete(this);
@@ -326,27 +261,27 @@ export class ToolGroupComponent extends Container {
326
261
  }
327
262
 
328
263
  removeTool(tool: any): void {
329
- this.settledCache = undefined;
264
+ this.clearPaintCache();
330
265
  const index = this.children.indexOf(tool);
331
266
  if (index >= 0) this.children.splice(index, 1);
332
267
  if (tool?.[PARENT_KEY] === this) delete tool[PARENT_KEY];
333
268
  }
334
269
 
335
270
  setExpanded(expanded: boolean): void {
336
- if (this._expanded !== expanded) this.settledCache = undefined;
271
+ if (this._expanded !== expanded) this.clearPaintCache();
337
272
  this._expanded = expanded;
338
273
  for (const tool of this.children)
339
274
  (tool as Component & { setExpanded?: (expanded: boolean) => void }).setExpanded?.(expanded);
340
275
  }
341
276
 
342
277
  setHintHovered(hovered: boolean): void {
343
- if (this.hintHovered !== hovered) this.settledCache = undefined;
278
+ if (this.hintHovered !== hovered) this.clearPaintCache();
344
279
  this.hintHovered = hovered;
345
280
  }
346
281
 
347
282
  /**
348
283
  * 展开时按局部行定位内部组件(null = 行属于 group 自身:空行/头行/尾行)。
349
- * 行数计算与 render 保持一致:预留背景 padding、分支和状态前缀后再过滤空行。
284
+ * 行数计算与 render 保持一致:宽度 width-2 + 空行过滤。
350
285
  */
351
286
  childAtRow(localRow: number, width: number): { component: any; row: number } | null {
352
287
  if (!this._expanded || localRow < 2) return null;
@@ -354,7 +289,7 @@ export class ToolGroupComponent extends Container {
354
289
  for (const tool of this.children) {
355
290
  let lines: string[] = [];
356
291
  try {
357
- const rendered = tool.render?.(groupChildWidth(width));
292
+ const rendered = tool.render?.(Math.max(1, width - 2));
358
293
  if (Array.isArray(rendered)) lines = visibleLines(rendered.map((line) => String(line)));
359
294
  } catch {
360
295
  lines = [];
@@ -369,7 +304,7 @@ export class ToolGroupComponent extends Container {
369
304
  }
370
305
 
371
306
  invalidate(): void {
372
- this.settledCache = undefined;
307
+ this.clearPaintCache();
373
308
  for (const tool of this.children) tool.invalidate?.();
374
309
  }
375
310
 
@@ -429,8 +364,7 @@ export class ToolGroupComponent extends Container {
429
364
  })
430
365
  .join(` ${fg("dim", "•")} `);
431
366
  const names = new Set(this.children.map(toolName));
432
- const label =
433
- names.size === 1 ? humanizeToolName(toolName(this.children[0])) : "Multiple Tools";
367
+ const label = names.size === 1 ? toolTitle(this.children[0]) : "Multiple Tools";
434
368
  const overall: ToolStatus = counts.error ? "error" : counts.pending ? "pending" : "success";
435
369
  if (
436
370
  (this.children as any[]).some((tool) => tool?.executionStarted && status(tool) === "pending")
@@ -439,16 +373,42 @@ export class ToolGroupComponent extends Container {
439
373
  const overallColor = overall === "pending" ? "accent" : overall;
440
374
  const nameList = names.size > 1 ? ` ${fg("dim", `• ${toolNameList(this.children)}`)}` : "";
441
375
  // 圆点保持 dim;hover 只高亮可点击文字。
442
- const hint = `${fg("dim", "•")} ${fg(this.hintHovered ? "text" : "dim", showMoreHintText())}`;
443
- const lines = [
444
- "",
445
- truncateToWidth(
446
- ` ${fg(overallColor, "●")} ${label}: ${countText}${nameList} ${hint}`,
447
- width,
448
- "…",
449
- ),
450
- ];
376
+ const hintText = this._expanded ? collapseHintText() : showMoreHintText();
377
+ const hint = `${fg("dim", "•")} ${fg(this.hintHovered ? "text" : "dim", hintText)}`;
378
+ // 行尾提示承载折叠分组的可点击区(collapsedHintHitbox 依赖行尾短语)。
379
+ // 空间不足时先省略工具名列表,再截断计数尾部,提示最后让位;
380
+ // 极窄视口连“圆点+标题+计数”都放不下时才回退整行截断。
381
+ const viewport = toolViewportWidth(width);
382
+ const head = ` ${fg(overallColor, "●")} ${label}: ${countText}`;
383
+ const hintWidth = visibleWidth(` ${hint}`);
384
+ const nameBudget = viewport - visibleWidth(head) - hintWidth;
385
+ const elidedName =
386
+ nameList && nameBudget < visibleWidth(nameList)
387
+ ? nameBudget >= 4
388
+ ? ` ${fg("dim", "…")}`
389
+ : ""
390
+ : nameList;
391
+ const headBudget = viewport - hintWidth - visibleWidth(elidedName);
392
+ const titleLine = `${visibleWidth(head) <= headBudget ? head : truncateToWidth(head, headBudget, "…")}${elidedName} ${hint}`;
393
+ const lines = ["", truncateToWidth(titleLine, viewport, "…")];
451
394
  const total = this.children.length;
395
+ const childPaints = this._expanded
396
+ ? (this.children as any[]).map((tool) => tool.render?.(Math.max(1, width - 2)))
397
+ : undefined;
398
+ if (this._expanded && childPaints) {
399
+ const expandedHit = this.expandedPaintCache;
400
+ if (
401
+ expandedHit &&
402
+ expandedHit.width === width &&
403
+ expandedHit.hover === this.hintHovered &&
404
+ expandedHit.theme === this.patch.theme &&
405
+ expandedHit.fullscreen === isToolTuiFullscreen() &&
406
+ expandedHit.paints.length === childPaints.length &&
407
+ expandedHit.paints.every((paint, index) => paint === childPaints[index])
408
+ ) {
409
+ return expandedHit.lines;
410
+ }
411
+ }
452
412
  const expandedLines: string[] = [];
453
413
  for (let index = 0; index < total; index++) {
454
414
  const tool = this.children[index];
@@ -460,17 +420,19 @@ export class ToolGroupComponent extends Container {
460
420
  const summary = toolSummary(tool);
461
421
  const prefix = ` ${fg("dim", branch)} ${fg(color, statusIcon(toolStatus))} `;
462
422
  const detail = fg("dim", summary.detail);
463
- const mainWidth = Math.max(0, width - visibleWidth(prefix) - visibleWidth(detail));
423
+ // 与单工具卡标题同宽,宽屏右侧留白一致
424
+ const rowWidth = toolViewportWidth(width);
425
+ const mainWidth = Math.max(0, rowWidth - visibleWidth(prefix) - visibleWidth(detail));
464
426
  lines.push(
465
427
  truncateToWidth(
466
- `${prefix}${fg("toolTitle", fitToolCallSummary(summary, mainWidth))}${detail}`,
467
- width,
428
+ `${prefix}${renderToolSummary(summary, mainWidth, fg)}${detail}`,
429
+ rowWidth,
468
430
  "",
469
431
  ),
470
432
  );
471
433
  continue;
472
434
  }
473
- const rendered = visibleLines(tool.render(groupChildWidth(width)));
435
+ const rendered = visibleLines(Array.isArray(childPaints?.[index]) ? childPaints[index] : []);
474
436
  if (rendered.length) {
475
437
  rendered[0] = stripLeadingStatusIcon(rendered[0])
476
438
  .replace(/^ +/, "")
@@ -479,7 +441,8 @@ export class ToolGroupComponent extends Container {
479
441
  const childLines = rendered.length ? rendered : [toolSummary(tool).main];
480
442
  for (let lineIndex = 0; lineIndex < childLines.length; lineIndex++) {
481
443
  const content =
482
- lineIndex === 0 ? childLines[lineIndex] : stripLeadingSpaces(childLines[lineIndex], 2);
444
+ // 续行只剥外层 Box 的 1 格 left pad,保留 Input/Output 相对缩进
445
+ lineIndex === 0 ? childLines[lineIndex] : stripLeadingSpaces(childLines[lineIndex], 1);
483
446
  const prefix =
484
447
  lineIndex === 0
485
448
  ? `${fg("dim", branch)} ${fg(color, statusIcon(toolStatus))} `
@@ -494,6 +457,14 @@ export class ToolGroupComponent extends Container {
494
457
  lines.push(paddedBackgroundRow(theme, backgroundSlot, line, width));
495
458
  }
496
459
  lines.push(paddedBackgroundRow(theme, backgroundSlot, "", width));
460
+ this.expandedPaintCache = {
461
+ width,
462
+ hover: this.hintHovered,
463
+ theme: this.patch.theme,
464
+ fullscreen: isToolTuiFullscreen(),
465
+ paints: childPaints ?? [],
466
+ lines,
467
+ };
497
468
  } else if (counts.pending === 0) {
498
469
  this.storeSettledCache(width, lines);
499
470
  }
@@ -520,7 +491,7 @@ function ungroup(patch: Patch): void {
520
491
  }
521
492
  }
522
493
 
523
- function normalizeGroup(group: ToolGroupComponent): void {
494
+ function normalizeGroup(patch: Patch, group: ToolGroupComponent): void {
524
495
  if (group.children.length > 1) return;
525
496
  const parent = (group as any)[PARENT_KEY];
526
497
  const index = parent?.children?.indexOf(group) ?? -1;
@@ -569,32 +540,17 @@ function maybeGroup(patch: Patch, parent: any, component: any): void {
569
540
  /** /reload 不会重新 addChild;扫描当前 mounted roots,把已有工具重新送入同一分组规则。 */
570
541
  function regroup(patch: Patch, root: any): void {
571
542
  if (!patch.active || !patch.enabled() || !root) return;
572
- const seen = new Set<any>();
573
- const visit = (value: any): void => {
574
- if (!value || typeof value !== "object" || seen.has(value)) return;
575
- seen.add(value);
576
- if (Array.isArray(value)) {
577
- for (const child of value) visit(child);
578
- return;
579
- }
543
+ walkComponentTree(root, (value: any) => {
544
+ // 分组卡与可分组工具是分组边界:不继续下钻(与原有遍历过滤一致)。
545
+ if (value instanceof ToolGroupComponent || isGroupable(value)) return false;
580
546
  const children = value.children;
581
547
  if (Array.isArray(children)) {
582
548
  for (const child of [...children]) {
583
549
  if (child && typeof child === "object") child[PARENT_KEY] = value;
584
550
  maybeGroup(patch, value, child);
585
551
  }
586
- for (const child of [...children]) {
587
- if (!(child instanceof ToolGroupComponent) && !isGroupable(child)) visit(child);
588
- }
589
552
  }
590
- try {
591
- const mounted = value.getMountedRoots?.();
592
- if (Array.isArray(mounted)) visit(mounted);
593
- } catch {
594
- // renderer 切换中的惰性 Proxy 可能暂时没有 mounted roots。
595
- }
596
- };
597
- visit(root);
553
+ });
598
554
  }
599
555
 
600
556
  export type ToolGroupingHooks = {
@@ -605,8 +561,7 @@ export type ToolGroupingHooks = {
605
561
 
606
562
  export function installToolGrouping(getEnabled: () => boolean): ToolGroupingHooks {
607
563
  const prototype = Container.prototype as any;
608
- const host = globalThis as any;
609
- const previous = host[PATCH_KEY] as Patch | undefined;
564
+ const previous = patchRegistry.get<Patch>(PATCH_KEY);
610
565
  if (previous) {
611
566
  previous.active = false;
612
567
  previous.enabled = () => false;
@@ -651,12 +606,12 @@ export function installToolGrouping(getEnabled: () => boolean): ToolGroupingHook
651
606
  const group = component?.[PARENT_KEY];
652
607
  if (group instanceof ToolGroupComponent && (group as any)[PARENT_KEY] === this) {
653
608
  group.removeTool(component);
654
- normalizeGroup(group);
609
+ normalizeGroup(patch, group);
655
610
  return;
656
611
  }
657
612
  const result = patch.original.removeChild.call(this, component);
658
613
  if (component?.[PARENT_KEY] === this) delete component[PARENT_KEY];
659
- if (this instanceof ToolGroupComponent) normalizeGroup(this);
614
+ if (this instanceof ToolGroupComponent) normalizeGroup(patch, this);
660
615
  if (component instanceof ToolGroupComponent) {
661
616
  for (const tool of component.releaseTools()) delete tool[PARENT_KEY];
662
617
  }
@@ -676,7 +631,7 @@ export function installToolGrouping(getEnabled: () => boolean): ToolGroupingHook
676
631
  prototype.addChild = patch.installed.addChild;
677
632
  prototype.removeChild = patch.installed.removeChild;
678
633
  prototype.clear = patch.installed.clear;
679
- host[PATCH_KEY] = patch;
634
+ patchRegistry.install(PATCH_KEY, patch);
680
635
  return {
681
636
  setTheme(theme: any) {
682
637
  patch.theme = theme;
@@ -0,0 +1,39 @@
1
+ import { humanizeToolLabel } from "./names.ts";
2
+
3
+ /**
4
+ * MCP 工具的识别与标题,单卡(default-mode)与分组行/分组头(grouping)共用。
5
+ *
6
+ * - 判定:工具名带 mcp 片段(`mcp` 网关、`mcpScript`、`mcp__<server>`),或 adapter 注册时
7
+ * 给的 label 以 `MCP:` 开头。默认/none/short 前缀模式下直连工具的名字里没有 mcp 片段
8
+ * (`github_search_code`、`search_code`),只能靠 label 判定。
9
+ * - 标题:具体工具用 adapter 暴露的真实工具名,不做二次格式化;只有 `mcp` / `mcpScript`
10
+ * 这两个入口没有具体工具名,跟普通工具一样人性化(MCP / MCP Script)。
11
+ * - 参数摘要与其它工具共用 toolCallSummary 的字段链,网关额外把 `args.tool` 提到标题后。
12
+ */
13
+
14
+ /** 单次 MCP 调用的识别信息。 */
15
+ export type McpToolCall = {
16
+ toolName: string;
17
+ /** 工具定义;默认/none/short 前缀模式要靠 label 判定,会话重放时可能缺失。 */
18
+ definition?: any;
19
+ };
20
+
21
+ const GATEWAY_TOOL_NAME = "mcp";
22
+ const SCRIPT_TOOL_NAME = "mcpScript";
23
+
24
+ export function isMcpToolDefinition(definition: any, toolName: string): boolean {
25
+ const label = typeof definition?.label === "string" ? definition.label.trim() : "";
26
+ if (/^MCP(?::|$)/i.test(label)) return true;
27
+ if (toolName === GATEWAY_TOOL_NAME || toolName === SCRIPT_TOOL_NAME) return true;
28
+ return /^mcp[_:-]|[_:-]mcp[_:-]/i.test(toolName);
29
+ }
30
+
31
+ /** 标题:MCP 工具返回标题(入口走人性化,其余用真实工具名);非 MCP 返回 undefined。 */
32
+ export function mcpToolTitle(call: McpToolCall): string | undefined {
33
+ const { toolName } = call;
34
+ if (!isMcpToolDefinition(call.definition, toolName)) return undefined;
35
+ if (toolName === GATEWAY_TOOL_NAME || toolName === SCRIPT_TOOL_NAME) {
36
+ return humanizeToolLabel(toolName);
37
+ }
38
+ return toolName;
39
+ }