@nghyane/arcane 0.1.12 → 0.1.14

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 (333) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/package.json +21 -70
  3. package/scripts/format-prompts.ts +1 -3
  4. package/src/cli/args.ts +2 -7
  5. package/src/cli/config-cli.ts +1 -1
  6. package/src/cli/plugin-cli.ts +1 -1
  7. package/src/cli/setup-cli.ts +1 -1
  8. package/src/cli/update-cli.ts +1 -1
  9. package/src/cli/web-search-cli.ts +1 -1
  10. package/src/cli.ts +0 -1
  11. package/src/commands/config.ts +1 -1
  12. package/src/commands/grep.ts +1 -1
  13. package/src/commands/jupyter.ts +1 -1
  14. package/src/commands/plugin.ts +1 -1
  15. package/src/commands/setup.ts +1 -1
  16. package/src/commands/shell.ts +1 -1
  17. package/src/commands/ssh.ts +1 -1
  18. package/src/commands/stats.ts +1 -1
  19. package/src/commands/update.ts +1 -1
  20. package/src/config/model-registry.ts +3 -4
  21. package/src/config/model-resolver.ts +36 -9
  22. package/src/config/prompt-templates.ts +1 -9
  23. package/src/config/settings-schema.ts +32 -88
  24. package/src/config/settings.ts +3 -4
  25. package/src/debug/index.ts +1 -1
  26. package/src/debug/log-formatting.ts +1 -1
  27. package/src/debug/log-viewer.ts +2 -2
  28. package/src/discovery/helpers.ts +13 -3
  29. package/src/exa/company.ts +2 -7
  30. package/src/exa/index.ts +1 -35
  31. package/src/exa/linkedin.ts +2 -7
  32. package/src/exa/mcp-client.ts +21 -11
  33. package/src/exa/render.ts +30 -190
  34. package/src/exa/researcher.ts +2 -12
  35. package/src/exa/search.ts +5 -25
  36. package/src/exa/types.ts +3 -3
  37. package/src/exec/bash-executor.ts +2 -1
  38. package/src/exec/non-interactive-env.ts +43 -0
  39. package/src/export/html/index.ts +1 -1
  40. package/src/extensibility/custom-tools/loader.ts +1 -1
  41. package/src/extensibility/custom-tools/types.ts +5 -1
  42. package/src/extensibility/custom-tools/wrapper.ts +1 -1
  43. package/src/extensibility/extensions/runner.ts +1 -1
  44. package/src/extensibility/extensions/types.ts +1 -1
  45. package/src/extensibility/extensions/wrapper.ts +7 -15
  46. package/src/extensibility/hooks/runner.ts +1 -1
  47. package/src/extensibility/hooks/types.ts +1 -1
  48. package/src/extensibility/plugins/doctor.ts +1 -1
  49. package/src/index.ts +13 -13
  50. package/src/lsp/index.ts +77 -24
  51. package/src/lsp/render.ts +34 -583
  52. package/src/lsp/types.ts +3 -3
  53. package/src/lsp/utils.ts +1 -1
  54. package/src/main.ts +1 -1
  55. package/src/mcp/tool-bridge.ts +1 -24
  56. package/src/modes/components/assistant-message.ts +7 -7
  57. package/src/modes/components/bash-execution.ts +48 -113
  58. package/src/modes/components/bordered-loader.ts +1 -1
  59. package/src/modes/components/branch-summary-message.ts +13 -10
  60. package/src/modes/components/compaction-summary-message.ts +14 -13
  61. package/src/modes/components/context-group.ts +106 -0
  62. package/src/modes/components/custom-message.ts +4 -5
  63. package/src/modes/components/diff.ts +2 -2
  64. package/src/modes/components/dynamic-border.ts +1 -1
  65. package/src/modes/components/extensions/extension-dashboard.ts +2 -2
  66. package/src/modes/components/extensions/extension-list.ts +1 -1
  67. package/src/modes/components/extensions/inspector-panel.ts +8 -3
  68. package/src/modes/components/footer.ts +2 -2
  69. package/src/modes/components/history-search.ts +1 -1
  70. package/src/modes/components/hook-editor.ts +1 -1
  71. package/src/modes/components/hook-input.ts +1 -1
  72. package/src/modes/components/hook-message.ts +4 -5
  73. package/src/modes/components/hook-selector.ts +1 -1
  74. package/src/modes/components/index.ts +0 -2
  75. package/src/modes/components/keybinding-hints.ts +1 -1
  76. package/src/modes/components/login-dialog.ts +1 -1
  77. package/src/modes/components/mcp-add-wizard.ts +1 -1
  78. package/src/modes/components/model-selector.ts +1 -1
  79. package/src/modes/components/oauth-selector.ts +1 -1
  80. package/src/modes/components/plugin-settings.ts +1 -1
  81. package/src/modes/components/python-execution.ts +49 -92
  82. package/src/modes/components/queue-mode-selector.ts +1 -1
  83. package/src/modes/components/session-selector.ts +1 -1
  84. package/src/modes/components/settings-defs.ts +5 -10
  85. package/src/modes/components/settings-selector.ts +1 -1
  86. package/src/modes/components/show-images-selector.ts +1 -1
  87. package/src/modes/components/skill-message.ts +4 -4
  88. package/src/modes/components/status-line/segments.ts +2 -2
  89. package/src/modes/components/status-line/separators.ts +1 -1
  90. package/src/modes/components/status-line-segment-editor.ts +1 -1
  91. package/src/modes/components/status-line.ts +1 -1
  92. package/src/modes/components/theme-selector.ts +1 -1
  93. package/src/modes/components/thinking-selector.ts +1 -1
  94. package/src/modes/components/todo-display.ts +2 -4
  95. package/src/modes/components/todo-reminder.ts +4 -4
  96. package/src/modes/components/tool-execution.ts +118 -440
  97. package/src/modes/components/tool-image-display.ts +107 -0
  98. package/src/modes/components/tree-selector.ts +2 -2
  99. package/src/modes/components/ttsr-notification.ts +4 -17
  100. package/src/modes/components/user-message-selector.ts +1 -1
  101. package/src/modes/components/user-message.ts +9 -10
  102. package/src/modes/components/welcome.ts +1 -1
  103. package/src/modes/controllers/command-controller.ts +1 -1
  104. package/src/modes/controllers/event-controller.ts +58 -187
  105. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  106. package/src/modes/controllers/input-controller.ts +3 -1
  107. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  108. package/src/modes/controllers/selector-controller.ts +3 -26
  109. package/src/modes/controllers/ssh-command-controller.ts +1 -1
  110. package/src/modes/interactive-mode.ts +3 -7
  111. package/src/modes/print-mode.ts +5 -5
  112. package/src/modes/rpc/rpc-mode.ts +1 -1
  113. package/src/modes/types.ts +1 -2
  114. package/src/modes/utils/ui-helpers.ts +34 -32
  115. package/src/patch/edit-tool.ts +742 -0
  116. package/src/patch/index.ts +32 -898
  117. package/src/patch/schemas.ts +208 -0
  118. package/src/patch/shared.ts +83 -151
  119. package/src/prompts/agents/explore.md +22 -37
  120. package/src/prompts/agents/frontmatter.md +1 -1
  121. package/src/prompts/agents/init.md +2 -2
  122. package/src/prompts/agents/librarian.md +30 -21
  123. package/src/prompts/agents/oracle.md +9 -2
  124. package/src/prompts/agents/reviewer.md +15 -49
  125. package/src/prompts/agents/task.md +17 -9
  126. package/src/prompts/compaction/branch-summary-context.md +1 -1
  127. package/src/prompts/compaction/branch-summary-preamble.md +1 -1
  128. package/src/prompts/compaction/branch-summary.md +4 -1
  129. package/src/prompts/compaction/compaction-short-summary.md +1 -1
  130. package/src/prompts/compaction/compaction-summary-context.md +1 -1
  131. package/src/prompts/compaction/compaction-summary.md +4 -1
  132. package/src/prompts/compaction/compaction-turn-prefix.md +1 -1
  133. package/src/prompts/compaction/compaction-update-summary.md +1 -1
  134. package/src/prompts/memories/consolidation.md +1 -1
  135. package/src/prompts/memories/read_path.md +1 -1
  136. package/src/prompts/memories/stage_one_input.md +1 -1
  137. package/src/prompts/memories/stage_one_system.md +1 -1
  138. package/src/prompts/review-request.md +1 -1
  139. package/src/prompts/system/agent-creation-architect.md +1 -1
  140. package/src/prompts/system/agent-creation-user.md +1 -1
  141. package/src/prompts/system/custom-system-prompt.md +1 -1
  142. package/src/prompts/system/file-operations.md +1 -1
  143. package/src/prompts/system/subagent-system-prompt.md +2 -2
  144. package/src/prompts/system/summarization-system.md +1 -1
  145. package/src/prompts/system/system-prompt.md +163 -178
  146. package/src/prompts/system/title-system.md +1 -1
  147. package/src/prompts/system/ttsr-interrupt.md +1 -1
  148. package/src/prompts/system/verification-reminder.md +6 -0
  149. package/src/prompts/system/web-search.md +1 -1
  150. package/src/sdk.ts +0 -9
  151. package/src/session/agent-session.ts +244 -1459
  152. package/src/session/auth-storage.ts +5 -0
  153. package/src/session/model-controller.ts +406 -0
  154. package/src/session/retry-utils.ts +71 -0
  155. package/src/session/session-manager.ts +22 -186
  156. package/src/session/session-types.ts +312 -0
  157. package/src/session/stats.ts +387 -0
  158. package/src/session/streaming-edit.ts +258 -0
  159. package/src/session/ttsr.ts +213 -0
  160. package/src/slash-commands/builtin-registry.ts +0 -8
  161. package/src/ssh/connection-manager.ts +1 -0
  162. package/src/stt/recorder.ts +2 -2
  163. package/src/system-prompt.ts +1 -14
  164. package/src/task/agents.ts +7 -33
  165. package/src/task/executor.ts +50 -438
  166. package/src/task/index.ts +104 -71
  167. package/src/task/progress-tracker.ts +390 -0
  168. package/src/task/render.ts +371 -187
  169. package/src/task/subprocess-tool-registry.ts +1 -1
  170. package/src/task/types.ts +14 -47
  171. package/src/tools/ask.ts +31 -42
  172. package/src/tools/bash-interactive.ts +4 -47
  173. package/src/tools/bash-interceptor.ts +2 -2
  174. package/src/tools/bash-normalize.ts +1 -1
  175. package/src/tools/bash-skill-urls.ts +2 -2
  176. package/src/tools/bash.ts +87 -136
  177. package/src/tools/browser.ts +54 -84
  178. package/src/tools/create-tools.ts +186 -0
  179. package/src/tools/default-renderer.ts +104 -0
  180. package/src/tools/explore.ts +11 -10
  181. package/src/tools/fetch.ts +24 -114
  182. package/src/tools/find.ts +48 -132
  183. package/src/tools/gemini-image.ts +5 -15
  184. package/src/tools/github.ts +450 -0
  185. package/src/tools/grep.ts +43 -179
  186. package/src/tools/index.ts +35 -198
  187. package/src/tools/json-tree.ts +3 -3
  188. package/src/tools/librarian.ts +18 -18
  189. package/src/tools/list-limit.ts +2 -2
  190. package/src/tools/notebook.ts +35 -87
  191. package/src/tools/oracle.ts +25 -25
  192. package/src/tools/output-meta.ts +89 -4
  193. package/src/tools/output-utils.ts +2 -2
  194. package/src/tools/python.ts +86 -637
  195. package/src/tools/read.ts +36 -119
  196. package/src/tools/reviewer-tool.ts +19 -21
  197. package/src/tools/search-code.ts +128 -0
  198. package/src/tools/ssh.ts +67 -126
  199. package/src/tools/subagent-tool.ts +197 -123
  200. package/src/tools/todo-write.ts +15 -31
  201. package/src/tools/tool-errors.ts +0 -30
  202. package/src/tools/undo-edit.ts +30 -67
  203. package/src/tools/write.ts +78 -127
  204. package/src/tui/code-cell.ts +4 -4
  205. package/src/tui/file-list.ts +2 -2
  206. package/src/tui/output-block.ts +1 -1
  207. package/src/tui/status-line.ts +1 -1
  208. package/src/tui/tree-list.ts +2 -2
  209. package/src/tui/types.ts +1 -1
  210. package/src/tui/utils.ts +1 -1
  211. package/src/{tools → ui}/render-utils.ts +87 -126
  212. package/src/utils/external-editor.ts +4 -4
  213. package/src/utils/file-mentions.ts +1 -1
  214. package/src/utils/index.ts +30 -0
  215. package/src/utils/tools-manager.ts +9 -19
  216. package/src/web/github-client.ts +290 -0
  217. package/src/web/scrapers/github.ts +11 -62
  218. package/src/web/search/auth.ts +1 -3
  219. package/src/web/search/index.ts +85 -49
  220. package/src/web/search/provider.ts +11 -16
  221. package/src/web/search/providers/grep.ts +160 -0
  222. package/src/web/search/render.ts +48 -235
  223. package/src/web/search/types.ts +1 -1
  224. package/src/commands/commit.ts +0 -36
  225. package/src/commit/agentic/agent.ts +0 -311
  226. package/src/commit/agentic/fallback.ts +0 -96
  227. package/src/commit/agentic/index.ts +0 -359
  228. package/src/commit/agentic/prompts/analyze-file.md +0 -22
  229. package/src/commit/agentic/prompts/session-user.md +0 -25
  230. package/src/commit/agentic/prompts/split-confirm.md +0 -1
  231. package/src/commit/agentic/prompts/system.md +0 -38
  232. package/src/commit/agentic/state.ts +0 -69
  233. package/src/commit/agentic/tools/analyze-file.ts +0 -118
  234. package/src/commit/agentic/tools/git-file-diff.ts +0 -194
  235. package/src/commit/agentic/tools/git-hunk.ts +0 -50
  236. package/src/commit/agentic/tools/git-overview.ts +0 -84
  237. package/src/commit/agentic/tools/index.ts +0 -56
  238. package/src/commit/agentic/tools/propose-changelog.ts +0 -128
  239. package/src/commit/agentic/tools/propose-commit.ts +0 -154
  240. package/src/commit/agentic/tools/recent-commits.ts +0 -81
  241. package/src/commit/agentic/tools/split-commit.ts +0 -280
  242. package/src/commit/agentic/topo-sort.ts +0 -44
  243. package/src/commit/agentic/trivial.ts +0 -51
  244. package/src/commit/agentic/validation.ts +0 -200
  245. package/src/commit/analysis/conventional.ts +0 -165
  246. package/src/commit/analysis/index.ts +0 -4
  247. package/src/commit/analysis/scope.ts +0 -242
  248. package/src/commit/analysis/summary.ts +0 -112
  249. package/src/commit/analysis/validation.ts +0 -66
  250. package/src/commit/changelog/detect.ts +0 -37
  251. package/src/commit/changelog/generate.ts +0 -110
  252. package/src/commit/changelog/index.ts +0 -234
  253. package/src/commit/changelog/parse.ts +0 -44
  254. package/src/commit/cli.ts +0 -93
  255. package/src/commit/git/diff.ts +0 -148
  256. package/src/commit/git/errors.ts +0 -9
  257. package/src/commit/git/index.ts +0 -211
  258. package/src/commit/git/operations.ts +0 -54
  259. package/src/commit/index.ts +0 -5
  260. package/src/commit/map-reduce/index.ts +0 -64
  261. package/src/commit/map-reduce/map-phase.ts +0 -178
  262. package/src/commit/map-reduce/reduce-phase.ts +0 -145
  263. package/src/commit/map-reduce/utils.ts +0 -9
  264. package/src/commit/message.ts +0 -11
  265. package/src/commit/model-selection.ts +0 -69
  266. package/src/commit/pipeline.ts +0 -243
  267. package/src/commit/prompts/analysis-system.md +0 -148
  268. package/src/commit/prompts/analysis-user.md +0 -38
  269. package/src/commit/prompts/changelog-system.md +0 -50
  270. package/src/commit/prompts/changelog-user.md +0 -18
  271. package/src/commit/prompts/file-observer-system.md +0 -24
  272. package/src/commit/prompts/file-observer-user.md +0 -8
  273. package/src/commit/prompts/reduce-system.md +0 -50
  274. package/src/commit/prompts/reduce-user.md +0 -17
  275. package/src/commit/prompts/summary-retry.md +0 -3
  276. package/src/commit/prompts/summary-system.md +0 -38
  277. package/src/commit/prompts/summary-user.md +0 -13
  278. package/src/commit/prompts/types-description.md +0 -2
  279. package/src/commit/types.ts +0 -109
  280. package/src/commit/utils/exclusions.ts +0 -42
  281. package/src/mcp/render.ts +0 -123
  282. package/src/modes/components/agent-dashboard.ts +0 -1130
  283. package/src/modes/components/codemode-group.ts +0 -369
  284. package/src/modes/components/read-tool-group.ts +0 -119
  285. package/src/modes/components/visual-truncate.ts +0 -63
  286. package/src/prompts/system/subagent-user-prompt.md +0 -8
  287. package/src/prompts/tools/ask.md +0 -44
  288. package/src/prompts/tools/bash.md +0 -24
  289. package/src/prompts/tools/browser.md +0 -33
  290. package/src/prompts/tools/calculator.md +0 -12
  291. package/src/prompts/tools/explore.md +0 -29
  292. package/src/prompts/tools/fetch.md +0 -16
  293. package/src/prompts/tools/find.md +0 -18
  294. package/src/prompts/tools/gemini-image.md +0 -23
  295. package/src/prompts/tools/grep.md +0 -28
  296. package/src/prompts/tools/hashline.md +0 -232
  297. package/src/prompts/tools/librarian.md +0 -24
  298. package/src/prompts/tools/lsp.md +0 -28
  299. package/src/prompts/tools/oracle.md +0 -26
  300. package/src/prompts/tools/patch.md +0 -74
  301. package/src/prompts/tools/python.md +0 -66
  302. package/src/prompts/tools/read.md +0 -36
  303. package/src/prompts/tools/replace.md +0 -38
  304. package/src/prompts/tools/reviewer.md +0 -41
  305. package/src/prompts/tools/ssh.md +0 -51
  306. package/src/prompts/tools/task-summary.md +0 -28
  307. package/src/prompts/tools/task.md +0 -146
  308. package/src/prompts/tools/todo-write.md +0 -65
  309. package/src/prompts/tools/undo-edit.md +0 -7
  310. package/src/prompts/tools/web-search.md +0 -19
  311. package/src/prompts/tools/write.md +0 -18
  312. package/src/task/batch.ts +0 -102
  313. package/src/task/discovery.ts +0 -126
  314. package/src/task/parallel.ts +0 -84
  315. package/src/task/template.ts +0 -32
  316. package/src/tools/calculator.ts +0 -537
  317. package/src/tools/jtd-to-typescript.ts +0 -198
  318. package/src/tools/renderers.ts +0 -60
  319. package/src/tools/tool-result.ts +0 -86
  320. /package/src/{modes/theme → theme}/dark.json +0 -0
  321. /package/src/{modes/theme → theme}/defaults/dark-catppuccin.json +0 -0
  322. /package/src/{modes/theme → theme}/defaults/dark-dracula.json +0 -0
  323. /package/src/{modes/theme → theme}/defaults/dark-gruvbox.json +0 -0
  324. /package/src/{modes/theme → theme}/defaults/dark-solarized.json +0 -0
  325. /package/src/{modes/theme → theme}/defaults/dark-tokyo-night.json +0 -0
  326. /package/src/{modes/theme → theme}/defaults/index.ts +0 -0
  327. /package/src/{modes/theme → theme}/defaults/light-catppuccin.json +0 -0
  328. /package/src/{modes/theme → theme}/defaults/light-github.json +0 -0
  329. /package/src/{modes/theme → theme}/defaults/light-solarized.json +0 -0
  330. /package/src/{modes/theme → theme}/light.json +0 -0
  331. /package/src/{modes/theme → theme}/mermaid-cache.ts +0 -0
  332. /package/src/{modes/theme → theme}/theme-schema.json +0 -0
  333. /package/src/{modes/theme → theme}/theme.ts +0 -0
@@ -5,63 +5,15 @@
5
5
  */
6
6
 
7
7
  import type { Component } from "@nghyane/arcane-tui";
8
- import { Text, visibleWidth, wrapTextWithAnsi } from "@nghyane/arcane-tui";
8
+ import { Text } from "@nghyane/arcane-tui";
9
9
  import type { RenderResultOptions } from "../../extensibility/custom-tools/types";
10
- import type { Theme } from "../../modes/theme/theme";
11
- import {
12
- formatAge,
13
- formatCount,
14
- formatExpandHint,
15
- formatMoreItems,
16
- formatStatusIcon,
17
- getDomain,
18
- getPreviewLines,
19
- PREVIEW_LIMITS,
20
- TRUNCATE_LENGTHS,
21
- truncateToWidth,
22
- } from "../../tools/render-utils";
23
- import { renderStatusLine, renderTreeList } from "../../tui";
24
- import { CachedOutputBlock } from "../../tui/output-block";
10
+ import type { Theme } from "../../theme/theme";
11
+ import { renderStatusLine } from "../../tui";
12
+ import { formatCount, formatMoreItems, getDomain, truncateToWidth } from "../../ui/render-utils";
25
13
  import { getSearchProvider } from "./provider";
26
14
  import type { SearchResponse } from "./types";
27
15
 
28
- const MAX_COLLAPSED_ANSWER_LINES = PREVIEW_LIMITS.COLLAPSED_LINES;
29
- const MAX_EXPANDED_ANSWER_LINES = PREVIEW_LIMITS.EXPANDED_LINES;
30
- const MAX_ANSWER_LINE_LEN = TRUNCATE_LENGTHS.LINE;
31
- const MAX_SNIPPET_LINES = 2;
32
- const MAX_SNIPPET_LINE_LEN = TRUNCATE_LENGTHS.LINE;
33
- const MAX_COLLAPSED_ITEMS = PREVIEW_LIMITS.COLLAPSED_ITEMS;
34
- const MAX_QUERY_PREVIEW = 2;
35
- const MAX_QUERY_LEN = 90;
36
- const MAX_REQUEST_ID_LEN = 36;
37
-
38
- function renderFallbackText(contentText: string, expanded: boolean, theme: Theme): Component {
39
- const lines = contentText.split("\n").filter(line => line.trim());
40
- const maxLines = expanded ? lines.length : 6;
41
- const displayLines = lines.slice(0, maxLines).map(line => truncateToWidth(line.trim(), 110));
42
- const remaining = lines.length - displayLines.length;
43
-
44
- const headerIcon = formatStatusIcon("warning", theme);
45
- const expandHint = formatExpandHint(theme, expanded, remaining > 0);
46
- let text = `${headerIcon} ${theme.fg("dim", "Response")}${expandHint}`;
47
-
48
- if (displayLines.length === 0) {
49
- text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", "No response data")}`;
50
- return new Text(text, 0, 0);
51
- }
52
-
53
- for (let i = 0; i < displayLines.length; i++) {
54
- const isLast = i === displayLines.length - 1 && remaining === 0;
55
- const branch = isLast ? theme.tree.last : theme.tree.branch;
56
- text += `\n ${theme.fg("dim", branch)} ${theme.fg("dim", displayLines[i])}`;
57
- }
58
-
59
- if (!expanded && remaining > 0) {
60
- text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, "line"))}`;
61
- }
62
-
63
- return new Text(text, 0, 0);
64
- }
16
+ const MAX_COLLAPSED_SOURCES = 5;
65
17
 
66
18
  export interface SearchRenderDetails {
67
19
  response: SearchResponse;
@@ -81,203 +33,70 @@ export function renderSearchResult(
81
33
  },
82
34
  ): Component {
83
35
  const details = result.details;
84
-
85
- // Handle error case
86
- if (details?.error) {
87
- return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
88
- }
89
-
90
- const rawText = result.content?.find(block => block.type === "text")?.text?.trim() ?? "";
91
36
  const response = details?.response;
92
- if (!response) {
93
- return renderFallbackText(rawText, options.expanded, theme);
94
- }
95
-
96
- const sources = Array.isArray(response.sources) ? response.sources : [];
37
+ const sources = Array.isArray(response?.sources) ? response.sources : [];
97
38
  const sourceCount = sources.length;
98
- const citations = Array.isArray(response.citations) ? response.citations : [];
99
- const citationCount = citations.length;
100
- const searchQueries = Array.isArray(response.searchQueries)
39
+ const searchQueries = Array.isArray(response?.searchQueries)
101
40
  ? response.searchQueries.filter(item => typeof item === "string")
102
41
  : [];
103
- const provider = response.provider;
104
-
105
- // Get answer text
106
- const answerText = typeof response.answer === "string" ? response.answer.trim() : "";
107
- const contentText = answerText || rawText;
108
- const answerLines = contentText
109
- ? contentText
110
- .split("\n")
111
- .filter(l => l.trim())
112
- .map(l => l.trim())
113
- : [];
114
- const totalAnswerLines = answerLines.length;
115
-
116
- const providerLabel = provider !== "none" ? getSearchProvider(provider).label : "None";
42
+ const provider = response?.provider;
43
+
44
+ const providerLabel = provider
45
+ ? provider === "none"
46
+ ? "None"
47
+ : provider === "grep"
48
+ ? "grep.app"
49
+ : getSearchProvider(provider).label
50
+ : "auto";
117
51
  const queryPreview = args?.query
118
52
  ? truncateToWidth(args.query, 80)
119
53
  : searchQueries[0]
120
54
  ? truncateToWidth(searchQueries[0], 80)
121
55
  : undefined;
56
+
122
57
  const header = renderStatusLine(
123
58
  {
124
- icon: sourceCount > 0 ? "success" : "warning",
59
+ icon: sourceCount > 0 ? "success" : details?.error ? "error" : "warning",
125
60
  title: "Web Search",
126
- description: providerLabel,
127
- meta: [formatCount("source", sourceCount)],
61
+ description: queryPreview,
62
+ meta: [formatCount("source", sourceCount), providerLabel],
128
63
  },
129
64
  theme,
130
65
  );
131
66
 
132
- const metaLines: string[] = [];
133
- metaLines.push(`${theme.fg("muted", "Provider:")} ${theme.fg("text", providerLabel)}`);
134
- if (response.authMode)
135
- metaLines.push(
136
- `${theme.fg("muted", "Auth:")} ${theme.fg("text", response.authMode === "oauth" ? "OAuth" : response.authMode === "api_key" ? "API key" : response.authMode)}`,
137
- );
138
- if (response.model) metaLines.push(`${theme.fg("muted", "Model:")} ${theme.fg("text", response.model)}`);
139
- metaLines.push(`${theme.fg("muted", "Sources:")} ${theme.fg("text", String(sourceCount))}`);
140
- if (citationCount > 0)
141
- metaLines.push(`${theme.fg("muted", "Citations:")} ${theme.fg("text", String(citationCount))}`);
142
- if (response.usage) {
143
- const usageParts: string[] = [];
144
- if (response.usage.inputTokens !== undefined) usageParts.push(`in ${response.usage.inputTokens}`);
145
- if (response.usage.outputTokens !== undefined) usageParts.push(`out ${response.usage.outputTokens}`);
146
- if (response.usage.totalTokens !== undefined) usageParts.push(`total ${response.usage.totalTokens}`);
147
- if (response.usage.searchRequests !== undefined) usageParts.push(`search ${response.usage.searchRequests}`);
148
- if (usageParts.length > 0)
149
- metaLines.push(`${theme.fg("muted", "Usage:")} ${theme.fg("text", usageParts.join(theme.sep.dot))}`);
150
- }
151
- if (response.requestId) {
152
- metaLines.push(
153
- `${theme.fg("muted", "Request:")} ${theme.fg("text", truncateToWidth(response.requestId, MAX_REQUEST_ID_LEN))}`,
154
- );
155
- }
156
- if (searchQueries.length > 0) {
157
- const queriesPreview = searchQueries.slice(0, MAX_QUERY_PREVIEW);
158
- const queryList = queriesPreview.map(q => truncateToWidth(q, MAX_QUERY_LEN));
159
- const suffix = searchQueries.length > queriesPreview.length ? "…" : "";
160
- metaLines.push(`${theme.fg("muted", "Queries:")} ${theme.fg("text", queryList.join("; "))}${suffix}`);
161
- }
67
+ let text = header;
162
68
 
163
- const outputBlock = new CachedOutputBlock();
164
-
165
- return {
166
- render(width: number): string[] {
167
- // Read mutable state at render time
168
- const { expanded } = options;
69
+ if (details?.error) {
70
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("error", details.error)}`;
71
+ return new Text(text, 0, 0);
72
+ }
169
73
 
170
- // Expanded-dependent computations
171
- const answerLimit = expanded ? MAX_EXPANDED_ANSWER_LINES : MAX_COLLAPSED_ANSWER_LINES;
172
- const answerPreview = contentText
173
- ? args?.allowLongAnswer
174
- ? answerLines.slice(0, args.maxAnswerLines ?? answerLines.length)
175
- : getPreviewLines(contentText, answerLimit, MAX_ANSWER_LINE_LEN)
176
- : [];
177
- const remainingAnswer = totalAnswerLines - answerPreview.length;
74
+ const { expanded } = options;
75
+ const maxItems = expanded ? sources.length : Math.min(sources.length, MAX_COLLAPSED_SOURCES);
76
+ const remaining = sources.length - maxItems;
178
77
 
179
- const sourceTree = renderTreeList(
180
- {
181
- items: sources,
182
- expanded,
183
- maxCollapsed: MAX_COLLAPSED_ITEMS,
184
- itemType: "source",
185
- renderItem: src => {
186
- const titleText =
187
- typeof src.title === "string" && src.title.trim()
188
- ? src.title
189
- : typeof src.url === "string" && src.url.trim()
190
- ? src.url
191
- : "Untitled";
192
- const title = truncateToWidth(titleText, 70);
193
- const url = typeof src.url === "string" ? src.url : "";
194
- const domain = url ? getDomain(url) : "";
195
- const age =
196
- formatAge(src.ageSeconds) || (typeof src.publishedDate === "string" ? src.publishedDate : "");
197
- const metaParts: string[] = [];
198
- if (domain) metaParts.push(theme.fg("dim", `(${domain})`));
199
- if (typeof src.author === "string" && src.author.trim())
200
- metaParts.push(theme.fg("muted", src.author));
201
- if (age) metaParts.push(theme.fg("muted", age));
202
- const metaSep = theme.fg("dim", theme.sep.dot);
203
- const metaSuffix = metaParts.length > 0 ? ` ${metaParts.join(metaSep)}` : "";
204
- const srcLines: string[] = [`${theme.fg("accent", title)}${metaSuffix}`];
205
- const snippetText = typeof src.snippet === "string" ? src.snippet : "";
206
- if (snippetText.trim()) {
207
- const snippetLines = getPreviewLines(snippetText, MAX_SNIPPET_LINES, MAX_SNIPPET_LINE_LEN);
208
- for (const snippetLine of snippetLines) {
209
- srcLines.push(theme.fg("muted", `${theme.format.dash} ${snippetLine}`));
210
- }
211
- }
212
- if (url) srcLines.push(theme.fg("mdLinkUrl", url));
213
- return srcLines;
214
- },
215
- },
216
- theme,
217
- );
78
+ for (let i = 0; i < maxItems; i++) {
79
+ const src = sources[i];
80
+ const isLast = i === maxItems - 1 && remaining === 0;
81
+ const branch = isLast ? theme.tree.last : theme.tree.branch;
82
+ const titleText =
83
+ typeof src.title === "string" && src.title.trim()
84
+ ? src.title
85
+ : typeof src.url === "string" && src.url.trim()
86
+ ? src.url
87
+ : "Untitled";
88
+ const title = truncateToWidth(titleText, 70);
89
+ const url = typeof src.url === "string" ? src.url : "";
90
+ const domain = url ? getDomain(url) : "";
91
+ const domainPart = domain ? ` ${theme.fg("dim", `(${domain})`)}` : "";
92
+ text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", title)}${domainPart}`;
93
+ }
218
94
 
219
- // Build answer section
220
- const answerState = sourceCount > 0 ? "success" : "warning";
221
- const borderColor: "warning" | "dim" = answerState === "warning" ? "warning" : "dim";
222
- const border = (t: string) => theme.fg(borderColor, t);
223
- const contentPrefix = border(`${theme.boxSharp.vertical} `);
224
- const contentSuffix = border(theme.boxSharp.vertical);
225
- const contentWidth = Math.max(0, width - visibleWidth(contentPrefix) - visibleWidth(contentSuffix));
226
- const answerTreeLines = answerPreview.length > 0 ? answerPreview : ["No answer text returned"];
227
- const answerTree = renderTreeList(
228
- {
229
- items: answerTreeLines,
230
- expanded: true,
231
- maxCollapsed: answerTreeLines.length,
232
- itemType: "line",
233
- renderItem: (line, context) => {
234
- const coloredLine =
235
- line === "No answer text returned" ? theme.fg("muted", line) : theme.fg("dim", line);
236
- if (!args?.allowLongAnswer) {
237
- return coloredLine;
238
- }
239
- const prefixWidth = visibleWidth(context.continuePrefix);
240
- const wrapWidth = Math.max(10, contentWidth - prefixWidth);
241
- return wrapTextWithAnsi(coloredLine, wrapWidth);
242
- },
243
- },
244
- theme,
245
- );
246
- if (remainingAnswer > 0) {
247
- answerTree.push(theme.fg("muted", formatMoreItems(remainingAnswer, "line")));
248
- }
95
+ if (remaining > 0) {
96
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, "source"))}`;
97
+ }
249
98
 
250
- return outputBlock.render(
251
- {
252
- header,
253
- state: sourceCount > 0 ? "success" : "warning",
254
- sections: [
255
- ...(queryPreview
256
- ? [
257
- {
258
- lines: [`${theme.fg("muted", "Query:")} ${theme.fg("text", queryPreview)}`],
259
- },
260
- ]
261
- : []),
262
- {
263
- label: theme.fg("toolTitle", "Answer"),
264
- lines: answerTree,
265
- },
266
- {
267
- label: theme.fg("toolTitle", "Sources"),
268
- lines: sourceTree.length > 0 ? sourceTree : [theme.fg("muted", "No sources returned")],
269
- },
270
- { label: theme.fg("toolTitle", "Metadata"), lines: metaLines },
271
- ],
272
- width,
273
- },
274
- theme,
275
- );
276
- },
277
- invalidate() {
278
- outputBlock.invalidate();
279
- },
280
- };
99
+ return new Text(text, 0, 0);
281
100
  }
282
101
 
283
102
  /** Render web search call (query preview) */
@@ -291,9 +110,3 @@ export function renderSearchCall(
291
110
  const text = renderStatusLine({ icon: "pending", title: "Web Search", description: query, meta: [provider] }, theme);
292
111
  return new Text(text, 0, 0);
293
112
  }
294
-
295
- export const webSearchToolRenderer = {
296
- renderCall: renderSearchCall,
297
- renderResult: renderSearchResult,
298
- mergeCallAndResult: true,
299
- };
@@ -48,7 +48,7 @@ export interface SearchUsage {
48
48
 
49
49
  /** Unified response across providers */
50
50
  export interface SearchResponse {
51
- provider: SearchProviderId | "none";
51
+ provider: SearchProviderId | "grep" | "none";
52
52
  /** Synthesized answer text (anthropic, perplexity) */
53
53
  answer?: string;
54
54
  /** Search result sources */
@@ -1,36 +0,0 @@
1
- /**
2
- * Generate and optionally push a commit with changelog updates.
3
- */
4
- import { Command, Flags } from "@nghyane/arcane-utils/cli";
5
- import { runCommitCommand } from "../commit";
6
- import type { CommitCommandArgs } from "../commit/types";
7
- import { initTheme } from "../modes/theme/theme";
8
-
9
- export default class Commit extends Command {
10
- static description = "Generate a commit message and update changelogs";
11
-
12
- static flags = {
13
- push: Flags.boolean({ description: "Push after committing" }),
14
- "dry-run": Flags.boolean({ description: "Preview without committing" }),
15
- "no-changelog": Flags.boolean({ description: "Skip changelog updates" }),
16
- legacy: Flags.boolean({ description: "Use legacy deterministic pipeline" }),
17
- context: Flags.string({ char: "c", description: "Additional context for the model" }),
18
- model: Flags.string({ char: "m", description: "Override model selection" }),
19
- };
20
-
21
- async run(): Promise<void> {
22
- const { flags } = await this.parse(Commit);
23
-
24
- const cmd: CommitCommandArgs = {
25
- push: flags.push ?? false,
26
- dryRun: flags["dry-run"] ?? false,
27
- noChangelog: flags["no-changelog"] ?? false,
28
- legacy: flags.legacy,
29
- context: flags.context,
30
- model: flags.model,
31
- };
32
-
33
- await initTheme();
34
- await runCommitCommand(cmd);
35
- }
36
- }
@@ -1,311 +0,0 @@
1
- import type { Api, Model } from "@nghyane/arcane-ai";
2
- import { Markdown } from "@nghyane/arcane-tui";
3
- import chalk from "chalk";
4
- import type { ControlledGit } from "../../commit/git";
5
- import typesDescriptionPrompt from "../../commit/prompts/types-description.md" with { type: "text" };
6
- import type { ModelRegistry } from "../../config/model-registry";
7
- import { renderPromptTemplate } from "../../config/prompt-templates";
8
- import type { Settings } from "../../config/settings";
9
- import { getMarkdownTheme } from "../../modes/theme/theme";
10
- import { createAgentSession } from "../../sdk";
11
- import type { AgentSessionEvent } from "../../session/agent-session";
12
- import type { AuthStorage } from "../../session/auth-storage";
13
- import agentUserPrompt from "./prompts/session-user.md" with { type: "text" };
14
- import agentSystemPrompt from "./prompts/system.md" with { type: "text" };
15
- import type { CommitAgentState } from "./state";
16
- import { createCommitTools } from "./tools";
17
-
18
- export interface CommitAgentInput {
19
- cwd: string;
20
- git: ControlledGit;
21
- model: Model<Api>;
22
- settings: Settings;
23
- modelRegistry: ModelRegistry;
24
- authStorage: AuthStorage;
25
- userContext?: string;
26
- contextFiles?: Array<{ path: string; content: string }>;
27
- changelogTargets: string[];
28
- requireChangelog: boolean;
29
- diffText?: string;
30
- existingChangelogEntries?: ExistingChangelogEntries[];
31
- }
32
-
33
- export interface ExistingChangelogEntries {
34
- path: string;
35
- sections: Array<{ name: string; items: string[] }>;
36
- }
37
-
38
- export async function runCommitAgentSession(input: CommitAgentInput): Promise<CommitAgentState> {
39
- const typesDescription = renderPromptTemplate(typesDescriptionPrompt);
40
- const systemPrompt = renderPromptTemplate(agentSystemPrompt, {
41
- types_description: typesDescription,
42
- });
43
- const state: CommitAgentState = { diffText: input.diffText };
44
- const spawns = "quick_task";
45
- const tools = createCommitTools({
46
- cwd: input.cwd,
47
- git: input.git,
48
- authStorage: input.authStorage,
49
- modelRegistry: input.modelRegistry,
50
- settings: input.settings,
51
- spawns,
52
- state,
53
- changelogTargets: input.changelogTargets,
54
- enableAnalyzeFiles: true,
55
- });
56
-
57
- const { session } = await createAgentSession({
58
- cwd: input.cwd,
59
- authStorage: input.authStorage,
60
- modelRegistry: input.modelRegistry,
61
- settings: input.settings,
62
- model: input.model,
63
- systemPrompt,
64
- customTools: tools,
65
- enableLsp: false,
66
- enableMCP: false,
67
- hasUI: false,
68
- spawns,
69
- toolNames: ["__none__"],
70
- contextFiles: input.contextFiles,
71
- disableExtensionDiscovery: true,
72
- skills: [],
73
- promptTemplates: [],
74
- slashCommands: [],
75
- });
76
- let toolCalls = 0;
77
- let messageCount = 0;
78
- let isThinking = false;
79
- let thinkingLineActive = false;
80
- const toolArgsById = new Map<string, { name: string; args?: Record<string, unknown> }>();
81
- const writeThinkingLine = (text: string) => {
82
- const line = chalk.dim(`… ${text}`);
83
- process.stdout.write(`\r\x1b[2K${line}`);
84
- thinkingLineActive = true;
85
- };
86
- const clearThinkingLine = () => {
87
- if (!thinkingLineActive) return;
88
- process.stdout.write("\r\x1b[2K");
89
- thinkingLineActive = false;
90
- };
91
- const unsubscribe = session.subscribe((event: AgentSessionEvent) => {
92
- switch (event.type) {
93
- case "message_start":
94
- if (event.message.role === "assistant") {
95
- isThinking = true;
96
- thinkingLineActive = false;
97
- }
98
- break;
99
- case "message_update": {
100
- if (event.message?.role !== "assistant") break;
101
- const preview = extractMessagePreview(event.message?.content ?? []);
102
- if (!preview) break;
103
- writeThinkingLine(preview);
104
- break;
105
- }
106
- case "tool_execution_start":
107
- toolCalls += 1;
108
- toolArgsById.set(event.toolCallId, { name: event.toolName, args: event.args });
109
- break;
110
- case "message_end": {
111
- const role = event.message?.role;
112
- if (role === "assistant") {
113
- messageCount += 1;
114
- isThinking = false;
115
- clearThinkingLine();
116
- const assistantMessage = event.message as { stopReason?: string; errorMessage?: string };
117
- if (assistantMessage.stopReason === "error" && assistantMessage.errorMessage) {
118
- writeStdout(`● Error: ${assistantMessage.errorMessage}`);
119
- }
120
- const messageText = extractMessageText(event.message?.content ?? []);
121
- if (messageText) {
122
- writeAssistantMessage(messageText);
123
- }
124
- }
125
- break;
126
- }
127
- case "tool_execution_end": {
128
- const stored = toolArgsById.get(event.toolCallId) ?? { name: event.toolName };
129
- toolArgsById.delete(event.toolCallId);
130
- clearThinkingLine();
131
- const toolLabel = formatToolLabel(stored.name);
132
- const symbol = event.isError ? "" : "";
133
- writeStdout(`${symbol} ${toolLabel}`);
134
- const argsLines = formatToolArgs(stored.args);
135
- if (argsLines.length > 0) {
136
- writeStdout(formatToolArgsBlock(argsLines));
137
- }
138
- break;
139
- }
140
- case "agent_end":
141
- if (isThinking) {
142
- isThinking = false;
143
- }
144
- writeStdout(`● agent finished (${messageCount} messages, ${toolCalls} tools)`);
145
- break;
146
- default:
147
- break;
148
- }
149
- });
150
-
151
- try {
152
- const prompt = renderPromptTemplate(agentUserPrompt, {
153
- user_context: input.userContext,
154
- changelog_targets: input.changelogTargets.length > 0 ? input.changelogTargets.join("\n") : undefined,
155
- existing_changelog_entries: input.existingChangelogEntries,
156
- });
157
- const MAX_RETRIES = 3;
158
- let retryCount = 0;
159
- const needsChangelog = input.requireChangelog && input.changelogTargets.length > 0;
160
-
161
- await session.prompt(prompt, { expandPromptTemplates: false });
162
- while (retryCount < MAX_RETRIES && !isProposalComplete(state, needsChangelog)) {
163
- retryCount += 1;
164
- const reminder = buildReminderMessage(state, needsChangelog, retryCount, MAX_RETRIES);
165
- await session.prompt(reminder, { expandPromptTemplates: false });
166
- }
167
-
168
- return state;
169
- } finally {
170
- unsubscribe();
171
- await session.dispose();
172
- }
173
- }
174
-
175
- function writeStdout(message: string): void {
176
- process.stdout.write(`${message}\n`);
177
- }
178
-
179
- function extractMessagePreview(content: Array<{ type: string; text?: string }>): string | null {
180
- const textBlocks = content
181
- .filter(block => block.type === "text" && typeof block.text === "string")
182
- .map(block => block.text?.trim())
183
- .filter((value): value is string => Boolean(value));
184
- if (textBlocks.length === 0) return null;
185
- const combined = textBlocks.join(" ").replace(/\s+/g, " ").trim();
186
- return truncateToolArg(combined);
187
- }
188
-
189
- function extractMessageText(content: Array<{ type: string; text?: string }>): string | null {
190
- const textBlocks = content
191
- .filter(block => block.type === "text" && typeof block.text === "string")
192
- .map(block => block.text ?? "")
193
- .filter(value => value.trim().length > 0);
194
- if (textBlocks.length === 0) return null;
195
- return textBlocks.join("\n").trim();
196
- }
197
-
198
- function writeAssistantMessage(message: string): void {
199
- const lines = renderMarkdownLines(message);
200
- if (lines.length === 0) return;
201
- let firstContentIndex = lines.findIndex(line => line.trim().length > 0);
202
- if (firstContentIndex === -1) {
203
- firstContentIndex = 0;
204
- }
205
- for (const [index, line] of lines.entries()) {
206
- const prefix = index === firstContentIndex ? "● " : " ";
207
- writeStdout(`${prefix}${line}`.trimEnd());
208
- }
209
- }
210
-
211
- function renderMarkdownLines(message: string): string[] {
212
- const width = Math.max(40, process.stdout.columns ?? 100);
213
- const markdown = new Markdown(message, 0, 0, getMarkdownTheme());
214
- return markdown.render(width);
215
- }
216
-
217
- function formatToolLabel(toolName: string): string {
218
- const displayName = toolName
219
- .split(/[_-]/)
220
- .map(segment => segment.charAt(0).toUpperCase() + segment.slice(1))
221
- .join("");
222
- return displayName;
223
- }
224
-
225
- function formatToolArgs(args?: Record<string, unknown>): string[] {
226
- if (!args || Object.keys(args).length === 0) return [];
227
- const lines: string[] = [];
228
- const visit = (value: unknown, keyPath: string) => {
229
- if (value === null || value === undefined) return;
230
- if (Array.isArray(value)) {
231
- if (value.length === 0) return;
232
- const rendered = value.map(item => renderPrimitive(item)).filter(Boolean);
233
- if (rendered.length > 0) {
234
- lines.push(`${keyPath}: ${rendered.join(", ")}`);
235
- }
236
- return;
237
- }
238
- if (typeof value === "object") {
239
- const entries = Object.entries(value as Record<string, unknown>);
240
- if (entries.length === 0) return;
241
- for (const [childKey, childValue] of entries) {
242
- visit(childValue, `${keyPath}.${childKey}`);
243
- }
244
- return;
245
- }
246
- const rendered = renderPrimitive(value);
247
- if (rendered) {
248
- lines.push(`${keyPath}: ${rendered}`);
249
- }
250
- };
251
- for (const [key, value] of Object.entries(args)) {
252
- visit(value, key);
253
- }
254
- return lines;
255
- }
256
-
257
- function renderPrimitive(value: unknown): string | null {
258
- if (value === null || value === undefined) return null;
259
- if (typeof value === "string") {
260
- const trimmed = value.trim();
261
- return trimmed.length > 0 ? trimmed : null;
262
- }
263
- if (typeof value === "number" || typeof value === "boolean") {
264
- return String(value);
265
- }
266
- return null;
267
- }
268
-
269
- function formatToolArgsBlock(lines: string[]): string {
270
- return lines
271
- .map((line, index) => {
272
- if (index === 0) return ` ⎿ ${line}`;
273
- const branch = index === lines.length - 1 ? "└" : "├";
274
- return ` ${branch} ${line}`;
275
- })
276
- .join("\n");
277
- }
278
-
279
- function isProposalComplete(state: CommitAgentState, requireChangelog: boolean): boolean {
280
- const hasCommit = Boolean(state.proposal ?? state.splitProposal);
281
- const hasChangelog = !requireChangelog || Boolean(state.changelogProposal);
282
- return hasCommit && hasChangelog;
283
- }
284
-
285
- function buildReminderMessage(
286
- state: CommitAgentState,
287
- requireChangelog: boolean,
288
- retryCount: number,
289
- maxRetries: number,
290
- ): string {
291
- const missing: string[] = [];
292
- if (!state.proposal && !state.splitProposal) {
293
- missing.push("commit proposal (propose_commit or split_commit)");
294
- }
295
- if (requireChangelog && !state.changelogProposal) {
296
- missing.push("changelog entries (propose_changelog)");
297
- }
298
- return `<system-reminder>
299
- CRITICAL: You must call the required tools before finishing.
300
-
301
- Missing: ${missing.join(", ") || "none"}.
302
- Reminder ${retryCount} of ${maxRetries}.
303
-
304
- Call the missing tool(s) now.
305
- </system-reminder>`;
306
- }
307
-
308
- function truncateToolArg(value: string): string {
309
- if (value.length <= 40) return value;
310
- return `${value.slice(0, 39)}…`;
311
- }