@nghyane/arcane 0.1.13 → 0.1.15

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 (303) 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/index.ts +1 -35
  30. package/src/exa/render.ts +30 -190
  31. package/src/export/html/index.ts +1 -1
  32. package/src/extensibility/custom-tools/loader.ts +1 -1
  33. package/src/extensibility/custom-tools/types.ts +5 -1
  34. package/src/extensibility/custom-tools/wrapper.ts +1 -1
  35. package/src/extensibility/extensions/runner.ts +1 -1
  36. package/src/extensibility/extensions/types.ts +1 -1
  37. package/src/extensibility/extensions/wrapper.ts +7 -15
  38. package/src/extensibility/hooks/runner.ts +1 -1
  39. package/src/extensibility/hooks/types.ts +1 -1
  40. package/src/extensibility/plugins/doctor.ts +1 -1
  41. package/src/index.ts +13 -13
  42. package/src/lsp/index.ts +77 -24
  43. package/src/lsp/render.ts +34 -583
  44. package/src/lsp/types.ts +3 -3
  45. package/src/lsp/utils.ts +1 -1
  46. package/src/main.ts +1 -1
  47. package/src/mcp/tool-bridge.ts +1 -24
  48. package/src/modes/components/assistant-message.ts +7 -7
  49. package/src/modes/components/bash-execution.ts +50 -112
  50. package/src/modes/components/bordered-loader.ts +1 -1
  51. package/src/modes/components/branch-summary-message.ts +16 -10
  52. package/src/modes/components/compaction-summary-message.ts +20 -12
  53. package/src/modes/components/context-group.ts +106 -0
  54. package/src/modes/components/custom-message.ts +4 -5
  55. package/src/modes/components/diff.ts +2 -2
  56. package/src/modes/components/dynamic-border.ts +1 -1
  57. package/src/modes/components/extensions/extension-dashboard.ts +1 -1
  58. package/src/modes/components/extensions/extension-list.ts +1 -1
  59. package/src/modes/components/extensions/inspector-panel.ts +1 -1
  60. package/src/modes/components/footer.ts +2 -2
  61. package/src/modes/components/history-search.ts +1 -1
  62. package/src/modes/components/hook-editor.ts +1 -1
  63. package/src/modes/components/hook-input.ts +1 -1
  64. package/src/modes/components/hook-message.ts +4 -5
  65. package/src/modes/components/hook-selector.ts +1 -1
  66. package/src/modes/components/index.ts +0 -2
  67. package/src/modes/components/keybinding-hints.ts +1 -1
  68. package/src/modes/components/login-dialog.ts +1 -1
  69. package/src/modes/components/mcp-add-wizard.ts +1 -1
  70. package/src/modes/components/model-selector.ts +1 -1
  71. package/src/modes/components/oauth-selector.ts +1 -1
  72. package/src/modes/components/plugin-settings.ts +1 -1
  73. package/src/modes/components/python-execution.ts +51 -91
  74. package/src/modes/components/queue-mode-selector.ts +1 -1
  75. package/src/modes/components/session-selector.ts +1 -1
  76. package/src/modes/components/settings-defs.ts +5 -10
  77. package/src/modes/components/settings-selector.ts +1 -1
  78. package/src/modes/components/show-images-selector.ts +1 -1
  79. package/src/modes/components/skill-message.ts +4 -4
  80. package/src/modes/components/status-line/segments.ts +2 -2
  81. package/src/modes/components/status-line/separators.ts +1 -1
  82. package/src/modes/components/status-line-segment-editor.ts +1 -1
  83. package/src/modes/components/status-line.ts +1 -1
  84. package/src/modes/components/theme-selector.ts +1 -1
  85. package/src/modes/components/thinking-selector.ts +1 -1
  86. package/src/modes/components/todo-display.ts +2 -4
  87. package/src/modes/components/todo-reminder.ts +4 -4
  88. package/src/modes/components/tool-execution.ts +118 -440
  89. package/src/modes/components/tool-image-display.ts +107 -0
  90. package/src/modes/components/tree-selector.ts +2 -2
  91. package/src/modes/components/ttsr-notification.ts +4 -17
  92. package/src/modes/components/user-message-selector.ts +1 -1
  93. package/src/modes/components/user-message.ts +9 -10
  94. package/src/modes/components/welcome.ts +1 -1
  95. package/src/modes/controllers/command-controller.ts +1 -1
  96. package/src/modes/controllers/event-controller.ts +58 -187
  97. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  98. package/src/modes/controllers/input-controller.ts +3 -1
  99. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  100. package/src/modes/controllers/selector-controller.ts +3 -26
  101. package/src/modes/controllers/ssh-command-controller.ts +1 -1
  102. package/src/modes/interactive-mode.ts +3 -7
  103. package/src/modes/print-mode.ts +5 -5
  104. package/src/modes/rpc/rpc-mode.ts +1 -1
  105. package/src/modes/types.ts +1 -2
  106. package/src/modes/utils/ui-helpers.ts +34 -32
  107. package/src/patch/edit-tool.ts +742 -0
  108. package/src/patch/index.ts +32 -898
  109. package/src/patch/schemas.ts +208 -0
  110. package/src/patch/shared.ts +83 -151
  111. package/src/prompts/agents/explore.md +22 -37
  112. package/src/prompts/agents/init.md +1 -1
  113. package/src/prompts/agents/librarian.md +29 -20
  114. package/src/prompts/agents/oracle.md +9 -2
  115. package/src/prompts/agents/reviewer.md +14 -48
  116. package/src/prompts/agents/task.md +16 -8
  117. package/src/prompts/compaction/branch-summary.md +4 -1
  118. package/src/prompts/compaction/compaction-summary.md +4 -1
  119. package/src/prompts/system/subagent-system-prompt.md +1 -1
  120. package/src/prompts/system/system-prompt.md +162 -178
  121. package/src/prompts/system/verification-reminder.md +6 -0
  122. package/src/sdk.ts +0 -9
  123. package/src/session/agent-session.ts +244 -1459
  124. package/src/session/model-controller.ts +406 -0
  125. package/src/session/retry-utils.ts +71 -0
  126. package/src/session/session-manager.ts +22 -186
  127. package/src/session/session-types.ts +312 -0
  128. package/src/session/stats.ts +387 -0
  129. package/src/session/streaming-edit.ts +258 -0
  130. package/src/session/ttsr.ts +213 -0
  131. package/src/slash-commands/builtin-registry.ts +0 -8
  132. package/src/stt/recorder.ts +2 -2
  133. package/src/system-prompt.ts +1 -14
  134. package/src/task/agents.ts +7 -33
  135. package/src/task/executor.ts +50 -438
  136. package/src/task/index.ts +104 -71
  137. package/src/task/progress-tracker.ts +390 -0
  138. package/src/task/render.ts +371 -187
  139. package/src/task/subprocess-tool-registry.ts +1 -1
  140. package/src/task/types.ts +14 -47
  141. package/src/tools/ask.ts +31 -42
  142. package/src/tools/bash-interactive.ts +2 -2
  143. package/src/tools/bash-interceptor.ts +2 -2
  144. package/src/tools/bash-normalize.ts +1 -1
  145. package/src/tools/bash-skill-urls.ts +2 -2
  146. package/src/tools/bash.ts +87 -136
  147. package/src/tools/browser.ts +54 -84
  148. package/src/tools/create-tools.ts +186 -0
  149. package/src/tools/default-renderer.ts +104 -0
  150. package/src/tools/explore.ts +11 -10
  151. package/src/tools/fetch.ts +24 -114
  152. package/src/tools/find.ts +48 -132
  153. package/src/tools/gemini-image.ts +5 -15
  154. package/src/tools/github.ts +450 -0
  155. package/src/tools/grep.ts +43 -179
  156. package/src/tools/index.ts +35 -198
  157. package/src/tools/json-tree.ts +3 -3
  158. package/src/tools/librarian.ts +18 -18
  159. package/src/tools/list-limit.ts +2 -2
  160. package/src/tools/notebook.ts +35 -87
  161. package/src/tools/oracle.ts +25 -25
  162. package/src/tools/output-meta.ts +89 -4
  163. package/src/tools/output-utils.ts +2 -2
  164. package/src/tools/python.ts +86 -637
  165. package/src/tools/read.ts +36 -119
  166. package/src/tools/reviewer-tool.ts +19 -21
  167. package/src/tools/search-code.ts +128 -0
  168. package/src/tools/ssh.ts +67 -126
  169. package/src/tools/subagent-tool.ts +197 -123
  170. package/src/tools/todo-write.ts +15 -31
  171. package/src/tools/tool-errors.ts +0 -30
  172. package/src/tools/undo-edit.ts +30 -67
  173. package/src/tools/write.ts +78 -127
  174. package/src/tui/code-cell.ts +4 -4
  175. package/src/tui/file-list.ts +2 -2
  176. package/src/tui/output-block.ts +1 -1
  177. package/src/tui/status-line.ts +1 -1
  178. package/src/tui/tree-list.ts +2 -2
  179. package/src/tui/types.ts +1 -1
  180. package/src/tui/utils.ts +1 -1
  181. package/src/{tools → ui}/render-utils.ts +87 -126
  182. package/src/utils/external-editor.ts +4 -4
  183. package/src/utils/file-mentions.ts +1 -1
  184. package/src/utils/index.ts +30 -0
  185. package/src/utils/tools-manager.ts +9 -19
  186. package/src/web/github-client.ts +290 -0
  187. package/src/web/scrapers/github.ts +11 -62
  188. package/src/web/search/auth.ts +1 -3
  189. package/src/web/search/index.ts +82 -46
  190. package/src/web/search/provider.ts +11 -16
  191. package/src/web/search/providers/grep.ts +160 -0
  192. package/src/web/search/render.ts +48 -235
  193. package/src/web/search/types.ts +1 -1
  194. package/src/commands/commit.ts +0 -36
  195. package/src/commit/agentic/agent.ts +0 -311
  196. package/src/commit/agentic/fallback.ts +0 -96
  197. package/src/commit/agentic/index.ts +0 -359
  198. package/src/commit/agentic/prompts/analyze-file.md +0 -22
  199. package/src/commit/agentic/prompts/session-user.md +0 -25
  200. package/src/commit/agentic/prompts/split-confirm.md +0 -1
  201. package/src/commit/agentic/prompts/system.md +0 -38
  202. package/src/commit/agentic/state.ts +0 -69
  203. package/src/commit/agentic/tools/analyze-file.ts +0 -118
  204. package/src/commit/agentic/tools/git-file-diff.ts +0 -194
  205. package/src/commit/agentic/tools/git-hunk.ts +0 -50
  206. package/src/commit/agentic/tools/git-overview.ts +0 -84
  207. package/src/commit/agentic/tools/index.ts +0 -56
  208. package/src/commit/agentic/tools/propose-changelog.ts +0 -128
  209. package/src/commit/agentic/tools/propose-commit.ts +0 -154
  210. package/src/commit/agentic/tools/recent-commits.ts +0 -81
  211. package/src/commit/agentic/tools/split-commit.ts +0 -280
  212. package/src/commit/agentic/topo-sort.ts +0 -44
  213. package/src/commit/agentic/trivial.ts +0 -51
  214. package/src/commit/agentic/validation.ts +0 -200
  215. package/src/commit/analysis/conventional.ts +0 -165
  216. package/src/commit/analysis/index.ts +0 -4
  217. package/src/commit/analysis/scope.ts +0 -242
  218. package/src/commit/analysis/summary.ts +0 -112
  219. package/src/commit/analysis/validation.ts +0 -66
  220. package/src/commit/changelog/detect.ts +0 -37
  221. package/src/commit/changelog/generate.ts +0 -110
  222. package/src/commit/changelog/index.ts +0 -234
  223. package/src/commit/changelog/parse.ts +0 -44
  224. package/src/commit/cli.ts +0 -93
  225. package/src/commit/git/diff.ts +0 -148
  226. package/src/commit/git/errors.ts +0 -9
  227. package/src/commit/git/index.ts +0 -211
  228. package/src/commit/git/operations.ts +0 -54
  229. package/src/commit/index.ts +0 -5
  230. package/src/commit/map-reduce/index.ts +0 -64
  231. package/src/commit/map-reduce/map-phase.ts +0 -178
  232. package/src/commit/map-reduce/reduce-phase.ts +0 -145
  233. package/src/commit/map-reduce/utils.ts +0 -9
  234. package/src/commit/message.ts +0 -11
  235. package/src/commit/model-selection.ts +0 -69
  236. package/src/commit/pipeline.ts +0 -243
  237. package/src/commit/prompts/analysis-system.md +0 -148
  238. package/src/commit/prompts/analysis-user.md +0 -38
  239. package/src/commit/prompts/changelog-system.md +0 -50
  240. package/src/commit/prompts/changelog-user.md +0 -18
  241. package/src/commit/prompts/file-observer-system.md +0 -24
  242. package/src/commit/prompts/file-observer-user.md +0 -8
  243. package/src/commit/prompts/reduce-system.md +0 -50
  244. package/src/commit/prompts/reduce-user.md +0 -17
  245. package/src/commit/prompts/summary-retry.md +0 -3
  246. package/src/commit/prompts/summary-system.md +0 -38
  247. package/src/commit/prompts/summary-user.md +0 -13
  248. package/src/commit/prompts/types-description.md +0 -2
  249. package/src/commit/types.ts +0 -109
  250. package/src/commit/utils/exclusions.ts +0 -42
  251. package/src/mcp/render.ts +0 -123
  252. package/src/modes/components/agent-dashboard.ts +0 -1130
  253. package/src/modes/components/codemode-group.ts +0 -369
  254. package/src/modes/components/read-tool-group.ts +0 -119
  255. package/src/modes/components/visual-truncate.ts +0 -63
  256. package/src/prompts/system/subagent-user-prompt.md +0 -8
  257. package/src/prompts/tools/ask.md +0 -44
  258. package/src/prompts/tools/bash.md +0 -24
  259. package/src/prompts/tools/browser.md +0 -33
  260. package/src/prompts/tools/calculator.md +0 -12
  261. package/src/prompts/tools/explore.md +0 -29
  262. package/src/prompts/tools/fetch.md +0 -16
  263. package/src/prompts/tools/find.md +0 -18
  264. package/src/prompts/tools/gemini-image.md +0 -23
  265. package/src/prompts/tools/grep.md +0 -28
  266. package/src/prompts/tools/hashline.md +0 -232
  267. package/src/prompts/tools/librarian.md +0 -24
  268. package/src/prompts/tools/lsp.md +0 -28
  269. package/src/prompts/tools/oracle.md +0 -26
  270. package/src/prompts/tools/patch.md +0 -74
  271. package/src/prompts/tools/python.md +0 -66
  272. package/src/prompts/tools/read.md +0 -36
  273. package/src/prompts/tools/replace.md +0 -38
  274. package/src/prompts/tools/reviewer.md +0 -41
  275. package/src/prompts/tools/ssh.md +0 -51
  276. package/src/prompts/tools/task-summary.md +0 -28
  277. package/src/prompts/tools/task.md +0 -146
  278. package/src/prompts/tools/todo-write.md +0 -65
  279. package/src/prompts/tools/undo-edit.md +0 -7
  280. package/src/prompts/tools/web-search.md +0 -19
  281. package/src/prompts/tools/write.md +0 -18
  282. package/src/task/batch.ts +0 -102
  283. package/src/task/discovery.ts +0 -126
  284. package/src/task/parallel.ts +0 -84
  285. package/src/task/template.ts +0 -32
  286. package/src/tools/calculator.ts +0 -537
  287. package/src/tools/jtd-to-typescript.ts +0 -198
  288. package/src/tools/renderers.ts +0 -60
  289. package/src/tools/tool-result.ts +0 -86
  290. /package/src/{modes/theme → theme}/dark.json +0 -0
  291. /package/src/{modes/theme → theme}/defaults/dark-catppuccin.json +0 -0
  292. /package/src/{modes/theme → theme}/defaults/dark-dracula.json +0 -0
  293. /package/src/{modes/theme → theme}/defaults/dark-gruvbox.json +0 -0
  294. /package/src/{modes/theme → theme}/defaults/dark-solarized.json +0 -0
  295. /package/src/{modes/theme → theme}/defaults/dark-tokyo-night.json +0 -0
  296. /package/src/{modes/theme → theme}/defaults/index.ts +0 -0
  297. /package/src/{modes/theme → theme}/defaults/light-catppuccin.json +0 -0
  298. /package/src/{modes/theme → theme}/defaults/light-github.json +0 -0
  299. /package/src/{modes/theme → theme}/defaults/light-solarized.json +0 -0
  300. /package/src/{modes/theme → theme}/light.json +0 -0
  301. /package/src/{modes/theme → theme}/mermaid-cache.ts +0 -0
  302. /package/src/{modes/theme → theme}/theme-schema.json +0 -0
  303. /package/src/{modes/theme → theme}/theme.ts +0 -0
@@ -9,29 +9,28 @@ import type { Component } from "@nghyane/arcane-tui";
9
9
  import { Text } from "@nghyane/arcane-tui";
10
10
  import { untilAborted } from "@nghyane/arcane-utils";
11
11
  import { type Static, Type } from "@sinclair/typebox";
12
- import { renderPromptTemplate } from "../config/prompt-templates";
13
12
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
14
13
  import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp";
15
- import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
16
- import writeDescription from "../prompts/tools/write.md" with { type: "text" };
17
14
  import type { ToolSession } from "../sdk";
18
- import { Ellipsis, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
19
- import { invalidateFsScanAfterWrite } from "./fs-cache-invalidation";
20
- import { type OutputMeta, outputMeta } from "./output-meta";
21
- import { resolveToCwd } from "./path-utils";
15
+ import { getLanguageFromPath, type Theme } from "../theme/theme";
16
+ import { renderStatusLine } from "../tui";
22
17
  import {
18
+ formatClickHint,
23
19
  formatDiagnostics,
24
- formatExpandHint,
25
- formatMoreItems,
26
20
  formatStatusIcon,
21
+ PREVIEW_LIMITS,
27
22
  replaceTabs,
28
23
  shortenPath,
29
- ToolUIKit,
30
- } from "./render-utils";
24
+ TRUNCATE_LENGTHS,
25
+ truncateToWidth,
26
+ } from "../ui/render-utils";
27
+ import { invalidateFsScanAfterWrite } from "./fs-cache-invalidation";
28
+ import { type OutputMeta, outputMeta } from "./output-meta";
29
+ import { resolveToCwd } from "./path-utils";
31
30
 
32
31
  const writeSchema = Type.Object({
33
- path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
34
- content: Type.String({ description: "Content to write to the file" }),
32
+ path: Type.String({ description: "File path (relative or absolute)" }),
33
+ content: Type.String({ description: "Complete file content to write" }),
35
34
  });
36
35
 
37
36
  export type WriteToolInput = Static<typeof writeSchema>;
@@ -69,10 +68,10 @@ type WriteParams = WriteToolInput;
69
68
  *
70
69
  * Creates or overwrites files with optional LSP formatting and diagnostics.
71
70
  */
72
- export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails> {
71
+ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails, Theme> {
73
72
  readonly name = "write";
74
73
  readonly label = "Write";
75
- readonly description: string;
74
+ description = "Create a new file";
76
75
  readonly parameters = writeSchema;
77
76
  readonly nonAbortable = true;
78
77
  readonly concurrency = "exclusive";
@@ -86,7 +85,6 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
86
85
  this.#writethrough = enableLsp
87
86
  ? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics })
88
87
  : writethroughNoop;
89
- this.description = renderPromptTemplate(writeDescription);
90
88
  }
91
89
 
92
90
  async execute(
@@ -122,73 +120,8 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
122
120
  };
123
121
  });
124
122
  }
125
- }
126
-
127
- // =============================================================================
128
- // TUI Renderer
129
- // =============================================================================
130
-
131
- interface WriteRenderArgs {
132
- path?: string;
133
- file_path?: string;
134
- content?: string;
135
- }
136
123
 
137
- const WRITE_PREVIEW_LINES = 6;
138
- const WRITE_STREAMING_PREVIEW_LINES = 12;
139
-
140
- function countLines(text: string): number {
141
- if (!text) return 0;
142
- return text.split("\n").length;
143
- }
144
-
145
- function formatMetadataLine(lineCount: number | null, language: string | undefined, uiTheme: Theme): string {
146
- const icon = uiTheme.getLangIcon(language);
147
- if (lineCount !== null) {
148
- return uiTheme.fg("dim", `${icon} ${lineCount} lines`);
149
- }
150
- return uiTheme.fg("dim", `${icon}`);
151
- }
152
-
153
- function formatStreamingContent(content: string, uiTheme: Theme, ui: ToolUIKit): string {
154
- if (!content) return "";
155
- const lines = content.split("\n");
156
- const displayLines = lines.slice(-WRITE_STREAMING_PREVIEW_LINES);
157
- const hidden = lines.length - displayLines.length;
158
-
159
- let text = "\n\n";
160
- if (hidden > 0) {
161
- text += uiTheme.fg("dim", `… (${hidden} earlier lines)\n`);
162
- }
163
- for (const line of displayLines) {
164
- text += `${uiTheme.fg("toolOutput", ui.truncate(replaceTabs(line), 80))}\n`;
165
- }
166
- text += uiTheme.fg("dim", `… (streaming)`);
167
- return text;
168
- }
169
-
170
- function renderContentPreview(content: string, expanded: boolean, uiTheme: Theme, ui: ToolUIKit): string {
171
- if (!content) return "";
172
- const lines = content.split("\n");
173
- const maxLines = expanded ? lines.length : Math.min(lines.length, WRITE_PREVIEW_LINES);
174
- const displayLines = expanded ? lines : lines.slice(-maxLines);
175
- const hidden = lines.length - displayLines.length;
176
-
177
- let text = "\n\n";
178
- for (const line of displayLines) {
179
- text += `${uiTheme.fg("toolOutput", ui.truncate(replaceTabs(line), 80))}\n`;
180
- }
181
- if (!expanded && hidden > 0) {
182
- const hint = formatExpandHint(uiTheme, expanded, hidden > 0);
183
- const moreLine = `${formatMoreItems(hidden, "line")}${hint ? ` ${hint}` : ""}`;
184
- text += uiTheme.fg("dim", moreLine);
185
- }
186
- return text;
187
- }
188
-
189
- export const writeToolRenderer = {
190
124
  renderCall(args: WriteRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
191
- const ui = new ToolUIKit(uiTheme);
192
125
  const rawPath = args.file_path || args.path || "";
193
126
  const filePath = shortenPath(rawPath);
194
127
  const lang = getLanguageFromPath(rawPath) ?? "text";
@@ -197,17 +130,18 @@ export const writeToolRenderer = {
197
130
  const spinner =
198
131
  options?.spinnerFrame !== undefined ? formatStatusIcon("running", uiTheme, options.spinnerFrame) : "";
199
132
 
200
- let text = `${ui.title("Write")} ${spinner ? `${spinner} ` : ""}${langIcon} ${pathDisplay}`;
133
+ const title = uiTheme.fg("toolTitle", uiTheme.bold("Write"));
134
+ let text = `${title} ${spinner ? `${spinner} ` : ""}${langIcon} ${pathDisplay}`;
201
135
 
202
136
  if (!args.content) {
203
137
  return new Text(text, 0, 0);
204
138
  }
205
139
 
206
140
  // Show streaming preview of content (tail)
207
- text += formatStreamingContent(args.content, uiTheme, ui);
141
+ text += formatStreamingContent(args.content, uiTheme);
208
142
 
209
143
  return new Text(text, 0, 0);
210
- },
144
+ }
211
145
 
212
146
  renderResult(
213
147
  result: { content: Array<{ type: string; text?: string }>; details?: WriteToolDetails },
@@ -215,60 +149,77 @@ export const writeToolRenderer = {
215
149
  uiTheme: Theme,
216
150
  args?: WriteRenderArgs,
217
151
  ): Component {
218
- const ui = new ToolUIKit(uiTheme);
219
152
  const rawPath = args?.file_path || args?.path || "";
220
153
  const filePath = shortenPath(rawPath);
221
154
  const fileContent = args?.content || "";
222
155
  const lang = getLanguageFromPath(rawPath);
223
- const langIcon = uiTheme.fg("muted", uiTheme.getLangIcon(lang));
224
- const pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
225
156
  const lineCount = countLines(fileContent);
157
+ const diagnostics = result.details?.diagnostics;
158
+
159
+ const meta: string[] = [];
160
+ if (lineCount > 0) meta.push(`${lineCount} lines`);
161
+ if (lang) meta.push(lang);
226
162
 
227
- // Build header with status icon
228
163
  const header = renderStatusLine(
229
- {
230
- icon: "success",
231
- title: "Write",
232
- description: `${langIcon} ${pathDisplay}`,
233
- },
164
+ { icon: "success", title: "Write", description: filePath || "file", meta },
234
165
  uiTheme,
235
166
  );
236
- const metadataLine = formatMetadataLine(lineCount, lang ?? "text", uiTheme);
237
- const diagnostics = result.details?.diagnostics;
238
167
 
239
- let cached: RenderCache | undefined;
168
+ // Tree-style content preview
169
+ const lines = fileContent ? fileContent.split("\n") : [];
170
+ const expanded = options.expanded;
171
+ const displayLines = expanded ? lines : lines.slice(-PREVIEW_LIMITS.WRITE_TAIL);
172
+ const skipped = lines.length - displayLines.length;
173
+
174
+ const bodyLines: string[] = [];
175
+ if (displayLines.length > 0) {
176
+ if (skipped > 0) {
177
+ bodyLines.push(uiTheme.fg("dim", `… ${skipped} more lines`));
178
+ }
179
+ for (let i = 0; i < displayLines.length; i++) {
180
+ bodyLines.push(uiTheme.fg("toolOutput", replaceTabs(displayLines[i])));
181
+ }
182
+ if (!expanded && skipped > 0) {
183
+ bodyLines.push(formatClickHint(uiTheme));
184
+ }
185
+ }
186
+
187
+ if (diagnostics) {
188
+ const diagText = formatDiagnostics(diagnostics, expanded, uiTheme, (fp: string) =>
189
+ uiTheme.getLangIcon(getLanguageFromPath(fp)),
190
+ );
191
+ if (diagText.trim()) bodyLines.push(diagText.trim());
192
+ }
193
+
194
+ const all = bodyLines.length > 0 ? [header, ...bodyLines] : [header];
195
+ return new Text(all.join("\n"), 0, 0);
196
+ }
197
+ }
240
198
 
241
- return {
242
- render(width: number) {
243
- const { expanded } = options;
244
- const key = new Hasher().bool(expanded).u32(width).digest();
245
- if (cached?.key === key) return cached.lines;
199
+ interface WriteRenderArgs {
200
+ path?: string;
201
+ file_path?: string;
202
+ content?: string;
203
+ }
246
204
 
247
- let text = header;
248
- text += `\n${metadataLine}`;
249
- text += renderContentPreview(fileContent, expanded, uiTheme, ui);
205
+ function countLines(text: string): number {
206
+ if (!text) return 0;
207
+ return text.split("\n").length;
208
+ }
250
209
 
251
- if (diagnostics) {
252
- const diagText = formatDiagnostics(diagnostics, expanded, uiTheme, fp =>
253
- uiTheme.getLangIcon(getLanguageFromPath(fp)),
254
- );
255
- if (diagText.trim()) {
256
- const diagLines = diagText.split("\n");
257
- const firstNonEmpty = diagLines.findIndex(line => line.trim());
258
- if (firstNonEmpty >= 0) {
259
- text += `\n${diagLines.slice(firstNonEmpty).join("\n")}`;
260
- }
261
- }
262
- }
210
+ function formatStreamingContent(content: string, uiTheme: Theme): string {
211
+ if (!content) return "";
212
+ const lines = content.split("\n");
213
+ const displayLines = lines.slice(-PREVIEW_LIMITS.EXPANDED_LINES);
214
+ const hidden = lines.length - displayLines.length;
263
215
 
264
- const lines = text.split("\n").map(l => truncateToWidth(l, width, Ellipsis.Omit));
265
- cached = { key, lines };
266
- return lines;
267
- },
268
- invalidate() {
269
- cached = undefined;
270
- },
271
- };
272
- },
273
- mergeCallAndResult: true,
274
- };
216
+ let text = "\n\n";
217
+ if (hidden > 0) {
218
+ text += uiTheme.fg("dim", `… (${hidden} earlier lines)\n`);
219
+ }
220
+ for (const line of displayLines) {
221
+ text += `${uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line), TRUNCATE_LENGTHS.CONTENT))}\n`;
222
+ }
223
+ text += uiTheme.fg("dim", `… (streaming)`);
224
+ return text;
225
+ }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Render a code cell with optional output section.
3
3
  */
4
- import { highlightCode, type Theme } from "../modes/theme/theme";
5
- import { formatDuration, formatExpandHint, formatMoreItems, replaceTabs } from "../tools/render-utils";
4
+ import { highlightCode, type Theme } from "../theme/theme";
5
+ import { formatClickHint, formatDuration, formatMoreItems, replaceTabs } from "../ui/render-utils";
6
6
  import { renderOutputBlock } from "./output-block";
7
7
  import type { State } from "./types";
8
8
  import { getStateIcon } from "./utils";
@@ -78,7 +78,7 @@ export function renderCodeCell(options: CodeCellOptions, theme: Theme): string[]
78
78
  const codeLines = rawCodeLines.slice(0, maxCodeLines);
79
79
  const hiddenCodeLines = rawCodeLines.length - codeLines.length;
80
80
  if (hiddenCodeLines > 0) {
81
- const hint = formatExpandHint(theme, expanded, hiddenCodeLines > 0);
81
+ const hint = formatClickHint(theme, expanded, hiddenCodeLines > 0);
82
82
  const moreLine = `${formatMoreItems(hiddenCodeLines, "line")}${hint ? ` ${hint}` : ""}`;
83
83
  codeLines.push(theme.fg("dim", moreLine));
84
84
  }
@@ -93,7 +93,7 @@ export function renderCodeCell(options: CodeCellOptions, theme: Theme): string[]
93
93
  outputLines.push(...displayLines);
94
94
  const remaining = rawLines.length - maxLines;
95
95
  if (remaining > 0) {
96
- const hint = formatExpandHint(theme, expanded, remaining > 0);
96
+ const hint = formatClickHint(theme, expanded, remaining > 0);
97
97
  const moreLine = `${formatMoreItems(remaining, "line")}${hint ? ` ${hint}` : ""}`;
98
98
  outputLines.push(theme.fg("dim", moreLine));
99
99
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Render file listings with optional icons and metadata.
3
3
  */
4
- import type { Theme } from "../modes/theme/theme";
5
- import { getLanguageFromPath } from "../modes/theme/theme";
4
+ import type { Theme } from "../theme/theme";
5
+ import { getLanguageFromPath } from "../theme/theme";
6
6
  import { renderTreeList } from "./tree-list";
7
7
 
8
8
  export interface FileEntry {
@@ -2,7 +2,7 @@
2
2
  * Bordered output container with optional header and sections.
3
3
  */
4
4
  import { padding, visibleWidth } from "@nghyane/arcane-tui";
5
- import type { Theme } from "../modes/theme/theme";
5
+ import type { Theme } from "../theme/theme";
6
6
  import type { State } from "./types";
7
7
  import type { RenderCache } from "./utils";
8
8
  import { getStateBgColor, Hasher, padToWidth, truncateToWidth } from "./utils";
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Standardized status header rendering for tool output.
3
3
  */
4
- import type { Theme, ThemeColor } from "../modes/theme/theme";
4
+ import type { Theme, ThemeColor } from "../theme/theme";
5
5
  import type { IconType } from "./types";
6
6
  import { getStateIcon } from "./utils";
7
7
 
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Hierarchical tree list rendering helper.
3
3
  */
4
- import type { Theme } from "../modes/theme/theme";
5
- import { formatMoreItems, replaceTabs } from "../tools/render-utils";
4
+ import type { Theme } from "../theme/theme";
5
+ import { formatMoreItems, replaceTabs } from "../ui/render-utils";
6
6
  import type { TreeContext } from "./types";
7
7
  import { getTreeBranch, getTreeContinuePrefix } from "./utils";
8
8
 
package/src/tui/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shared types for TUI rendering components.
3
3
  */
4
- import type { Theme } from "../modes/theme/theme";
4
+ import type { Theme } from "../theme/theme";
5
5
 
6
6
  export type State = "pending" | "running" | "success" | "error" | "warning";
7
7
  export type IconType = "success" | "error" | "running" | "pending" | "warning" | "info";
package/src/tui/utils.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Shared helpers for tool-rendered UI components.
3
3
  */
4
4
  import { padding, visibleWidth } from "@nghyane/arcane-tui";
5
- import type { Theme, ThemeBg } from "../modes/theme/theme";
5
+ import type { Theme, ThemeBg } from "../theme/theme";
6
6
  import type { IconType, State } from "./types";
7
7
 
8
8
  export { Ellipsis, truncateToWidth } from "@nghyane/arcane-tui";
@@ -6,10 +6,69 @@
6
6
  */
7
7
  import * as os from "node:os";
8
8
  import { type Ellipsis, truncateToWidth } from "@nghyane/arcane-tui";
9
- import type { Theme } from "../modes/theme/theme";
9
+ import type { Theme } from "../theme/theme";
10
10
 
11
11
  export { Ellipsis, replaceTabs, truncateToWidth } from "@nghyane/arcane-tui";
12
12
 
13
+ // =============================================================================
14
+ // Tool Tiers
15
+ // =============================================================================
16
+
17
+ export type ToolTier = "quiet" | "action" | "interactive" | "subagent" | "default";
18
+
19
+ const TOOL_TIERS: Record<string, ToolTier> = {
20
+ // Quiet — dim, 1 line, no expand
21
+ read: "quiet",
22
+ grep: "quiet",
23
+ find: "quiet",
24
+ fetch: "quiet",
25
+ search_code: "quiet",
26
+ lsp: "quiet",
27
+ browser: "quiet",
28
+ github: "quiet",
29
+ notebook: "quiet",
30
+ undo_edit: "quiet",
31
+ generate_image: "quiet",
32
+
33
+ // Action — normal color, show output, some expandable
34
+ bash: "action",
35
+ ssh: "action",
36
+ python: "action",
37
+ edit: "action",
38
+ write: "action",
39
+ web_search: "action",
40
+ exa_search: "action",
41
+ exa_search_deep: "action",
42
+ exa_search_code: "action",
43
+ exa_crawl: "action",
44
+ exa_company: "action",
45
+ exa_linkedin: "action",
46
+ exa_researcher_start: "action",
47
+ exa_researcher_poll: "action",
48
+
49
+ // Interactive — inline result, no expand
50
+ ask: "interactive",
51
+ todo_write: "interactive",
52
+
53
+ // Subagent — tool history + conclusion
54
+ task: "subagent",
55
+ explore: "subagent",
56
+ librarian: "subagent",
57
+ oracle: "subagent",
58
+ code_review: "subagent",
59
+ };
60
+
61
+ export function getToolTier(toolName: string): ToolTier {
62
+ return TOOL_TIERS[toolName] ?? "default";
63
+ }
64
+
65
+ /** Tools that gather context (read-only information retrieval). Grouped into context batches. */
66
+ const CONTEXT_TOOLS = new Set(["read", "grep", "find", "fetch", "search_code", "lsp", "notebook"]);
67
+
68
+ export function isContextTool(toolName: string): boolean {
69
+ return CONTEXT_TOOLS.has(toolName);
70
+ }
71
+
13
72
  // =============================================================================
14
73
  // Standardized Display Constants
15
74
  // =============================================================================
@@ -23,13 +82,23 @@ export const PREVIEW_LIMITS = {
23
82
  /** Items (files, results) shown in collapsed view */
24
83
  COLLAPSED_ITEMS: 8,
25
84
  /** Output preview lines in collapsed view */
26
- OUTPUT_COLLAPSED: 3,
85
+ OUTPUT_COLLAPSED: 4,
27
86
  /** Output preview lines in expanded view */
28
- OUTPUT_EXPANDED: 10,
87
+ OUTPUT_EXPANDED: 12,
29
88
  /** Max hunks shown when collapsed (edit tool) */
30
89
  DIFF_COLLAPSED_HUNKS: 8,
31
90
  /** Max diff lines shown when collapsed (edit tool) */
32
- DIFF_COLLAPSED_LINES: 40,
91
+ DIFF_COLLAPSED_LINES: 20,
92
+ /** Tail lines for write tool collapsed view */
93
+ WRITE_TAIL: 6,
94
+ /** Streaming preview lines (edit/write) */
95
+ STREAMING_PREVIEW: 6,
96
+ /** Subagent tool history entries when streaming */
97
+ SUBAGENT_STREAMING_TOOLS: 4,
98
+ /** Subagent tool history entries when collapsed */
99
+ SUBAGENT_COLLAPSED_TOOLS: 3,
100
+ /** Subagent conclusion lines when collapsed */
101
+ SUBAGENT_CONCLUSION: 6,
33
102
  } as const;
34
103
 
35
104
  /** Truncation lengths for different content types */
@@ -44,11 +113,12 @@ export const TRUNCATE_LENGTHS = {
44
113
  LINE: 110,
45
114
  /** Very short (task previews, badges) */
46
115
  SHORT: 40,
116
+ /** Subagent error messages */
117
+ SUBAGENT_ERROR: 70,
118
+ /** Subagent tool args preview */
119
+ TOOL_ARGS: 50,
47
120
  } as const;
48
121
 
49
- /** Standard expand hint text */
50
- export const EXPAND_HINT = "(Ctrl+O for more)";
51
-
52
122
  // =============================================================================
53
123
  // Text Truncation Utilities
54
124
  // =============================================================================
@@ -167,16 +237,6 @@ export function formatStatusIcon(status: ToolUIStatus, theme: Theme, spinnerFram
167
237
  }
168
238
  }
169
239
 
170
- /**
171
- * Format the expand hint with proper theming.
172
- * Returns empty string if already expanded or there is nothing more to show.
173
- */
174
- export function formatExpandHint(theme: Theme, expanded?: boolean, hasMore?: boolean): string {
175
- if (expanded) return "";
176
- if (hasMore === false) return "";
177
- return theme.fg("dim", wrapBrackets(EXPAND_HINT, theme));
178
- }
179
-
180
240
  /**
181
241
  * Format a badge like [done] or [failed] with brackets and color.
182
242
  */
@@ -195,16 +255,14 @@ export function formatMoreItems(remaining: number, itemType: string): string {
195
255
  return `… ${safeRemaining} more ${pluralize(itemType, safeRemaining)}`;
196
256
  }
197
257
 
198
- export function formatMeta(meta: string[], theme: Theme): string {
199
- return meta.length > 0 ? ` ${theme.fg("muted", meta.join(theme.sep.dot))}` : "";
200
- }
201
-
202
- export function formatScope(scopePath: string | undefined, theme: Theme): string {
203
- return scopePath ? ` ${theme.fg("muted", `in ${scopePath}`)}` : "";
204
- }
205
-
206
- export function formatTruncationSuffix(truncated: boolean, theme: Theme): string {
207
- return truncated ? theme.fg("warning", " (truncated)") : "";
258
+ /**
259
+ * Render a subtle "click to expand" hint for truncated content.
260
+ * Returns empty string if already expanded or nothing to expand.
261
+ */
262
+ export function formatClickHint(theme: Theme, expanded?: boolean, hasMore?: boolean): string {
263
+ if (expanded) return "";
264
+ if (hasMore === false) return "";
265
+ return theme.fg("dim", wrapBrackets("click to expand", theme));
208
266
  }
209
267
 
210
268
  export function formatErrorMessage(message: string | undefined, theme: Theme): string {
@@ -216,110 +274,13 @@ export function formatEmptyMessage(message: string, theme: Theme): string {
216
274
  return `${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", message)}`;
217
275
  }
218
276
 
219
- // =============================================================================
220
- // Tool UI Kit
221
- // =============================================================================
222
-
223
- export type ToolUIStatus = "success" | "error" | "warning" | "info" | "pending" | "running" | "aborted";
277
+ type ToolUIStatus = "success" | "error" | "warning" | "info" | "pending" | "running" | "aborted";
224
278
  export type ToolUIColor = "success" | "error" | "warning" | "accent" | "muted";
225
279
 
226
- export interface ToolUITitleOptions {
227
- bold?: boolean;
228
- }
229
-
230
280
  // =============================================================================
231
281
  // Diagnostic Formatting
232
282
  // =============================================================================
233
283
 
234
- export class ToolUIKit {
235
- constructor(public theme: Theme) {}
236
-
237
- title(label: string, options?: ToolUITitleOptions): string {
238
- const content = options?.bold === false ? label : this.theme.bold(label);
239
- return this.theme.fg("toolTitle", content);
240
- }
241
-
242
- meta(meta: string[]): string {
243
- return formatMeta(meta, this.theme);
244
- }
245
-
246
- count(label: string, count: number): string {
247
- return formatCount(label, count);
248
- }
249
-
250
- moreItems(remaining: number, itemType: string): string {
251
- return formatMoreItems(remaining, itemType);
252
- }
253
-
254
- expandHint(expanded: boolean, hasMore: boolean): string {
255
- return formatExpandHint(this.theme, expanded, hasMore);
256
- }
257
-
258
- scope(scopePath?: string): string {
259
- return formatScope(scopePath, this.theme);
260
- }
261
-
262
- truncationSuffix(truncated: boolean): string {
263
- return formatTruncationSuffix(truncated, this.theme);
264
- }
265
-
266
- errorMessage(message: string | undefined): string {
267
- return formatErrorMessage(message, this.theme);
268
- }
269
-
270
- emptyMessage(message: string): string {
271
- return formatEmptyMessage(message, this.theme);
272
- }
273
-
274
- badge(label: string, color: ToolUIColor): string {
275
- return formatBadge(label, color, this.theme);
276
- }
277
-
278
- statusIcon(status: ToolUIStatus, spinnerFrame?: number): string {
279
- return formatStatusIcon(status, this.theme, spinnerFrame);
280
- }
281
-
282
- wrapBrackets(text: string): string {
283
- return wrapBrackets(text, this.theme);
284
- }
285
-
286
- truncate(text: string, maxLen: number): string {
287
- return truncateToWidth(text, maxLen);
288
- }
289
-
290
- previewLines(text: string, maxLines: number, maxLineLen: number): string[] {
291
- return getPreviewLines(text, maxLines, maxLineLen);
292
- }
293
-
294
- formatBytes(bytes: number): string {
295
- return formatBytes(bytes);
296
- }
297
-
298
- formatTokens(tokens: number): string {
299
- return formatTokens(tokens);
300
- }
301
-
302
- formatDuration(ms: number): string {
303
- return formatDuration(ms);
304
- }
305
-
306
- formatAge(ageSeconds: number | null | undefined): string {
307
- return formatAge(ageSeconds);
308
- }
309
-
310
- formatDiagnostics(
311
- diag: { errored: boolean; summary: string; messages: string[] },
312
- expanded: boolean,
313
- getLangIcon: (filePath: string) => string,
314
- ): string {
315
- return formatDiagnostics(diag, expanded, this.theme, getLangIcon);
316
- }
317
-
318
- formatDiffStats(added: number, removed: number, hunks: number): string {
319
- return formatDiffStats(added, removed, hunks, this.theme);
320
- }
321
- }
322
-
323
284
  interface ParsedDiagnostic {
324
285
  filePath: string;
325
286
  line: number;
@@ -477,7 +438,7 @@ export function formatDiagnostics(
477
438
  output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
478
439
  "muted",
479
440
  `… ${remaining} more`,
480
- )} ${formatExpandHint(theme)}`;
441
+ )} ${formatClickHint(theme)}`;
481
442
  }
482
443
 
483
444
  return output;
@@ -40,10 +40,10 @@ export async function openInEditor(
40
40
  const stdio = options?.stdio ?? ["inherit", "inherit", "inherit"];
41
41
 
42
42
  const child = spawn(editor, [...editorArgs, tmpFile], { stdio });
43
- const exitCode = await new Promise<number>((resolve, reject) => {
44
- child.once("exit", (code, signal) => resolve(code ?? (signal ? -1 : 0)));
45
- child.once("error", error => reject(error));
46
- });
43
+ const { promise: exitPromise, resolve, reject } = Promise.withResolvers<number>();
44
+ child.once("exit", (code, signal) => resolve(code ?? (signal ? -1 : 0)));
45
+ child.once("error", error => reject(error));
46
+ const exitCode = await exitPromise;
47
47
 
48
48
  if (exitCode === 0) {
49
49
  return (await Bun.file(tmpFile).text()).replace(/\n$/, "");
@@ -18,7 +18,7 @@ import {
18
18
  truncateStringToBytesFromStart,
19
19
  } from "../session/streaming-output";
20
20
  import { resolveReadPath } from "../tools/path-utils";
21
- import { formatAge } from "../tools/render-utils";
21
+ import { formatAge } from "../ui/render-utils";
22
22
  import { fuzzyMatch } from "./fuzzy";
23
23
  import { formatDimensionNote, resizeImage } from "./image-resize";
24
24
  import { detectSupportedImageMimeTypeFromFile } from "./mime";