@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
package/src/main.ts CHANGED
@@ -27,11 +27,11 @@ import { initializeWithSettings } from "./discovery";
27
27
  import { exportFromFile } from "./export/html";
28
28
  import type { ExtensionUIContext } from "./extensibility/extensions/types";
29
29
  import { InteractiveMode, runPrintMode, runRpcMode } from "./modes";
30
- import { initTheme, stopThemeWatcher } from "./modes/theme/theme";
31
30
  import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage } from "./sdk";
32
31
  import type { AgentSession } from "./session/agent-session";
33
32
  import { type SessionInfo, SessionManager } from "./session/session-manager";
34
33
  import { resolvePromptInput } from "./system-prompt";
34
+ import { initTheme, stopThemeWatcher } from "./theme/theme";
35
35
  import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog";
36
36
  import { printTimings, time } from "./utils/timings";
37
37
  import { VERSION } from "./version";
@@ -6,16 +6,9 @@
6
6
  import type { AgentToolUpdateCallback } from "@nghyane/arcane-agent";
7
7
  import type { TSchema } from "@sinclair/typebox";
8
8
  import type { SourceMeta } from "../capability/types";
9
- import type {
10
- CustomTool,
11
- CustomToolContext,
12
- CustomToolResult,
13
- RenderResultOptions,
14
- } from "../extensibility/custom-tools/types";
15
- import type { Theme } from "../modes/theme/theme";
9
+ import type { CustomTool, CustomToolContext, CustomToolResult } from "../extensibility/custom-tools/types";
16
10
  import { ToolAbortError, throwIfAborted } from "../tools/tool-errors";
17
11
  import { callTool } from "./client";
18
- import { renderMCPCall, renderMCPResult } from "./render";
19
12
  import type { MCPContent, MCPServerConnection, MCPToolDefinition } from "./types";
20
13
 
21
14
  function withAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
@@ -198,14 +191,6 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
198
191
  this.mcpServerName = connection.name;
199
192
  }
200
193
 
201
- renderCall(args: unknown, _options: RenderResultOptions, theme: Theme) {
202
- return renderMCPCall((args ?? {}) as Record<string, unknown>, theme, this.label);
203
- }
204
-
205
- renderResult(result: CustomToolResult<MCPToolDetails>, options: RenderResultOptions, theme: Theme, args?: unknown) {
206
- return renderMCPResult(result, options, theme, (args ?? {}) as Record<string, unknown>);
207
- }
208
-
209
194
  async execute(
210
195
  _toolCallId: string,
211
196
  params: unknown,
@@ -304,14 +289,6 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
304
289
  this.#fallbackProviderName = source?.providerName;
305
290
  }
306
291
 
307
- renderCall(args: unknown, _options: RenderResultOptions, theme: Theme) {
308
- return renderMCPCall((args ?? {}) as Record<string, unknown>, theme, this.label);
309
- }
310
-
311
- renderResult(result: CustomToolResult<MCPToolDetails>, options: RenderResultOptions, theme: Theme, args?: unknown) {
312
- return renderMCPResult(result, options, theme, (args ?? {}) as Record<string, unknown>);
313
- }
314
-
315
292
  async execute(
316
293
  _toolCallId: string,
317
294
  params: unknown,
@@ -1,8 +1,8 @@
1
1
  import type { AssistantMessage } from "@nghyane/arcane-ai";
2
2
  import { Container, Markdown, Spacer, TERMINAL, Text } from "@nghyane/arcane-tui";
3
3
  import { logger } from "@nghyane/arcane-utils";
4
- import { hasPendingMermaid, prerenderMermaid } from "../../modes/theme/mermaid-cache";
5
- import { getMarkdownTheme, theme } from "../../modes/theme/theme";
4
+ import { hasPendingMermaid, prerenderMermaid } from "../../theme/mermaid-cache";
5
+ import { getMarkdownTheme, theme } from "../../theme/theme";
6
6
 
7
7
  /**
8
8
  * Component that renders a complete assistant message
@@ -90,7 +90,7 @@ export class AssistantMessageComponent extends Container {
90
90
  if (content.type === "text" && content.text.trim()) {
91
91
  // Assistant text messages with no background - trim the text
92
92
  // Set paddingY=0 to avoid extra spacing before tool executions
93
- this.#contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, getMarkdownTheme()));
93
+ this.#contentContainer.addChild(new Markdown(content.text.trim(), 2, 0, getMarkdownTheme()));
94
94
  } else if (content.type === "thinking" && content.thinking.trim()) {
95
95
  // Add spacing only when another visible assistant content block follows.
96
96
  // This avoids a superfluous blank line before separately-rendered tool execution blocks.
@@ -100,14 +100,14 @@ export class AssistantMessageComponent extends Container {
100
100
 
101
101
  if (this.hideThinkingBlock) {
102
102
  // Show static "Thinking..." label when hidden
103
- this.#contentContainer.addChild(new Text(theme.italic(theme.fg("thinkingText", "Thinking...")), 1, 0));
103
+ this.#contentContainer.addChild(new Text(theme.italic(theme.fg("thinkingText", "Thinking...")), 2, 0));
104
104
  if (hasVisibleContentAfter) {
105
105
  this.#contentContainer.addChild(new Spacer(1));
106
106
  }
107
107
  } else {
108
108
  // Thinking traces in thinkingText color, italic
109
109
  this.#contentContainer.addChild(
110
- new Markdown(content.thinking.trim(), 1, 0, getMarkdownTheme(), {
110
+ new Markdown(content.thinking.trim(), 2, 0, getMarkdownTheme(), {
111
111
  color: (text: string) => theme.fg("thinkingText", text),
112
112
  italic: true,
113
113
  }),
@@ -133,11 +133,11 @@ export class AssistantMessageComponent extends Container {
133
133
  } else {
134
134
  this.#contentContainer.addChild(new Spacer(1));
135
135
  }
136
- this.#contentContainer.addChild(new Text(theme.fg("error", abortMessage), 1, 0));
136
+ this.#contentContainer.addChild(new Text(theme.fg("error", abortMessage), 2, 0));
137
137
  } else if (message.stopReason === "error") {
138
138
  const errorMsg = message.errorMessage || "Unknown error";
139
139
  this.#contentContainer.addChild(new Spacer(1));
140
- this.#contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0));
140
+ this.#contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 2, 0));
141
141
  }
142
142
  }
143
143
  }
@@ -1,17 +1,16 @@
1
1
  /**
2
2
  * Component for displaying bash command execution with streaming output.
3
+ * Tree-style output: tail 4 lines on success, all on error.
3
4
  */
4
5
 
5
6
  import { sanitizeText } from "@nghyane/arcane-natives";
6
7
  import { Container, Loader, Spacer, Text, type TUI } from "@nghyane/arcane-tui";
7
- import { getSymbolTheme, theme } from "../../modes/theme/theme";
8
- import { formatBytes } from "../../session/streaming-output";
8
+ import { getSymbolTheme, theme } from "../../theme/theme";
9
9
  import type { TruncationMeta } from "../../tools/output-meta";
10
- import { DynamicBorder } from "./dynamic-border";
11
- import { truncateToVisualLines } from "./visual-truncate";
10
+ import { renderStatusLine } from "../../tui/status-line";
11
+ import { formatClickHint, formatCount, replaceTabs } from "../../ui/render-utils";
12
12
 
13
- // Preview line limit when not expanded (matches tool execution behavior)
14
- const PREVIEW_LINES = 20;
13
+ const TAIL_LINES = 4;
15
14
  const MAX_DISPLAY_LINE_CHARS = 4000;
16
15
 
17
16
  export class BashExecutionComponent extends Container {
@@ -21,50 +20,37 @@ export class BashExecutionComponent extends Container {
21
20
  #loader: Loader;
22
21
  #truncation?: TruncationMeta;
23
22
  #expanded = false;
24
- #contentContainer: Container;
23
+ #headerText: Text;
24
+ #bodyText: Text;
25
25
 
26
26
  constructor(
27
27
  private readonly command: string,
28
28
  ui: TUI,
29
- excludeFromContext = false,
29
+ _excludeFromContext = false,
30
30
  ) {
31
31
  super();
32
-
33
- // Use dim border for excluded-from-context commands (!! prefix)
34
- const colorKey = excludeFromContext ? "dim" : "bashMode";
35
- const borderColor = (str: string) => theme.fg(colorKey, str);
36
-
37
- // Add spacer
38
32
  this.addChild(new Spacer(1));
39
33
 
40
- // Top border
41
- this.addChild(new DynamicBorder(borderColor));
42
-
43
- // Content container (holds dynamic content between borders)
44
- this.#contentContainer = new Container();
45
- this.addChild(this.#contentContainer);
34
+ this.#headerText = new Text(
35
+ renderStatusLine({ icon: "running", title: "Bash", description: `$ ${command}` }, theme),
36
+ 1,
37
+ 0,
38
+ );
39
+ this.addChild(this.#headerText);
46
40
 
47
- // Command header
48
- const header = new Text(theme.fg(colorKey, theme.bold(`$ ${command}`)), 1, 0);
49
- this.#contentContainer.addChild(header);
41
+ this.#bodyText = new Text("", 1, 0);
42
+ this.addChild(this.#bodyText);
50
43
 
51
- // Loader
52
44
  this.#loader = new Loader(
53
45
  ui,
54
- spinner => theme.fg(colorKey, spinner),
46
+ spinner => theme.fg("accent", spinner),
55
47
  text => theme.fg("muted", text),
56
- `Running (esc to cancel)`,
48
+ "Running\u2026 (esc to cancel)",
57
49
  getSymbolTheme().spinnerFrames,
58
50
  );
59
- this.#contentContainer.addChild(this.#loader);
60
-
61
- // Bottom border
62
- this.addChild(new DynamicBorder(borderColor));
51
+ this.addChild(this.#loader);
63
52
  }
64
53
 
65
- /**
66
- * Set whether the output is expanded (shows full output) or collapsed (preview only).
67
- */
68
54
  setExpanded(expanded: boolean): void {
69
55
  this.#expanded = expanded;
70
56
  this.#updateDisplay();
@@ -77,11 +63,8 @@ export class BashExecutionComponent extends Container {
77
63
 
78
64
  appendOutput(chunk: string): void {
79
65
  const clean = sanitizeText(chunk);
80
-
81
- // Append to output lines
82
66
  const newLines = clean.split("\n").map(line => this.#clampDisplayLine(line));
83
67
  if (this.#outputLines.length > 0 && newLines.length > 0) {
84
- // Append first chunk to last line (incomplete line continuation)
85
68
  this.#outputLines[this.#outputLines.length - 1] = this.#clampDisplayLine(
86
69
  `${this.#outputLines[this.#outputLines.length - 1]}${newLines[0]}`,
87
70
  );
@@ -89,7 +72,6 @@ export class BashExecutionComponent extends Container {
89
72
  } else {
90
73
  this.#outputLines.push(...newLines);
91
74
  }
92
-
93
75
  this.#updateDisplay();
94
76
  }
95
77
 
@@ -108,93 +90,55 @@ export class BashExecutionComponent extends Container {
108
90
  if (options?.output !== undefined) {
109
91
  this.#setOutput(options.output);
110
92
  }
111
-
112
- // Stop loader
113
93
  this.#loader.stop();
114
-
115
94
  this.#updateDisplay();
116
95
  }
117
96
 
118
97
  #updateDisplay(): void {
119
- const availableLines = this.#outputLines;
120
-
121
- // Apply preview truncation based on expanded state
122
- const previewLogicalLines = availableLines.slice(-PREVIEW_LINES);
123
- const hiddenLineCount = availableLines.length - previewLogicalLines.length;
124
-
125
- // Rebuild content container
126
- this.#contentContainer.clear();
127
-
128
- // Command header
129
- const header = new Text(theme.fg("bashMode", theme.bold(`$ ${this.command}`)), 1, 0);
130
- this.#contentContainer.addChild(header);
131
-
132
- // Output
133
- if (availableLines.length > 0) {
134
- if (this.#expanded) {
135
- // Show all lines
136
- const displayText = availableLines.map(line => theme.fg("muted", line)).join("\n");
137
- this.#contentContainer.addChild(new Text(`\n${displayText}`, 1, 0));
138
- } else {
139
- // Use shared visual truncation utility, recomputed per render width
140
- const styledOutput = previewLogicalLines.map(line => theme.fg("muted", line)).join("\n");
141
- const previewText = `\n${styledOutput}`;
142
- this.#contentContainer.addChild({
143
- render: (width: number) => {
144
- const { visualLines } = truncateToVisualLines(previewText, PREVIEW_LINES, width, 1);
145
- return visualLines;
146
- },
147
- invalidate: () => {},
148
- });
149
- }
98
+ const isError = this.#status === "error";
99
+ const isDone = this.#status !== "running";
100
+ const lines = this.#outputLines.filter(l => l.trim());
101
+ const total = lines.length;
102
+
103
+ // Update header
104
+ if (isDone) {
105
+ const icon = isError ? "error" : this.#status === "cancelled" ? "warning" : "success";
106
+ const meta: string[] = [];
107
+ if (isError && this.#exitCode !== undefined) meta.push(`exit ${this.#exitCode}`);
108
+ if (total > 0) meta.push(formatCount("line", total));
109
+ this.#headerText.setText(
110
+ renderStatusLine({ icon, title: "Bash", description: `$ ${this.command}`, meta }, theme),
111
+ );
150
112
  }
151
113
 
152
- // Loader or status
153
- if (this.#status === "running") {
154
- this.#contentContainer.addChild(this.#loader);
155
- } else {
156
- const statusParts: string[] = [];
114
+ // Build tree-style body
115
+ const bodyLines: string[] = [];
116
+ if (total > 0) {
117
+ const showAll = isError || this.#expanded;
118
+ const displayLines = showAll ? lines : lines.slice(-TAIL_LINES);
119
+ const skipped = total - displayLines.length;
157
120
 
158
- // Show how many lines are hidden (collapsed preview)
159
- if (hiddenLineCount > 0) {
160
- statusParts.push(theme.fg("dim", `… ${hiddenLineCount} more lines (ctrl+o to expand)`));
121
+ if (skipped > 0) {
122
+ bodyLines.push(theme.fg("dim", `\u2026 (${skipped} earlier lines)`));
161
123
  }
162
-
163
- if (this.#status === "cancelled") {
164
- statusParts.push(theme.fg("warning", "(cancelled)"));
165
- } else if (this.#status === "error") {
166
- statusParts.push(theme.fg("error", `(exit ${this.#exitCode})`));
124
+ for (let i = 0; i < displayLines.length; i++) {
125
+ bodyLines.push(theme.fg("toolOutput", replaceTabs(displayLines[i])));
167
126
  }
168
-
169
127
  if (this.#truncation) {
170
- const warnings: string[] = [];
171
- if (this.#truncation.artifactId) {
172
- warnings.push(`Full output: artifact://${this.#truncation.artifactId}`);
173
- }
174
- if (this.#truncation.truncatedBy === "lines") {
175
- warnings.push(
176
- `Truncated: showing ${this.#truncation.outputLines} of ${this.#truncation.totalLines} lines`,
177
- );
178
- } else {
179
- warnings.push(
180
- `Truncated: ${this.#truncation.outputLines} lines shown (${formatBytes(this.#truncation.outputBytes)} limit)`,
181
- );
182
- }
183
- statusParts.push(theme.fg("warning", warnings.join(". ")));
128
+ bodyLines.push(theme.fg("warning", "output truncated"));
184
129
  }
185
-
186
- if (statusParts.length > 0) {
187
- this.#contentContainer.addChild(new Text(`\n${statusParts.join("\n")}`, 1, 0));
130
+ if (!showAll && skipped > 0) {
131
+ bodyLines.push(formatClickHint(theme));
188
132
  }
189
133
  }
134
+
135
+ this.#bodyText.setText(bodyLines.length > 0 ? bodyLines.join("\n") : "");
190
136
  }
191
137
 
192
138
  #clampDisplayLine(line: string): string {
193
- if (line.length <= MAX_DISPLAY_LINE_CHARS) {
194
- return line;
195
- }
139
+ if (line.length <= MAX_DISPLAY_LINE_CHARS) return line;
196
140
  const omitted = line.length - MAX_DISPLAY_LINE_CHARS;
197
- return `${line.slice(0, MAX_DISPLAY_LINE_CHARS)} [${omitted} chars omitted]`;
141
+ return `${line.slice(0, MAX_DISPLAY_LINE_CHARS)}\u2026 [${omitted} chars omitted]`;
198
142
  }
199
143
 
200
144
  #setOutput(output: string): void {
@@ -202,16 +146,10 @@ export class BashExecutionComponent extends Container {
202
146
  this.#outputLines = clean ? clean.split("\n").map(line => this.#clampDisplayLine(line)) : [];
203
147
  }
204
148
 
205
- /**
206
- * Get the raw output for creating BashExecutionMessage.
207
- */
208
149
  getOutput(): string {
209
150
  return this.#outputLines.join("\n");
210
151
  }
211
152
 
212
- /**
213
- * Get the command that was executed.
214
- */
215
153
  getCommand(): string {
216
154
  return this.command;
217
155
  }
@@ -1,5 +1,5 @@
1
1
  import { CancellableLoader, Container, Spacer, Text, type TUI } from "@nghyane/arcane-tui";
2
- import type { Theme } from "../../modes/theme/theme";
2
+ import type { Theme } from "../../theme/theme";
3
3
  import { DynamicBorder } from "./dynamic-border";
4
4
 
5
5
  /** Loader wrapped with borders for hook UI */
@@ -1,16 +1,20 @@
1
- import { Box, Markdown, Spacer, Text } from "@nghyane/arcane-tui";
2
- import { getMarkdownTheme, theme } from "../../modes/theme/theme";
1
+ import { Container, LeftBorderBox, Markdown, Spacer, Text } from "@nghyane/arcane-tui";
3
2
  import type { BranchSummaryMessage } from "../../session/messages";
3
+ import { getMarkdownTheme, theme } from "../../theme/theme";
4
+ import { formatClickHint } from "../../ui/render-utils";
4
5
 
5
6
  /**
6
7
  * Component that renders a branch summary message with collapsed/expanded state.
7
- * Uses same background color as hook messages for visual consistency.
8
8
  */
9
- export class BranchSummaryMessageComponent extends Box {
9
+ export class BranchSummaryMessageComponent extends Container {
10
+ #box: LeftBorderBox;
10
11
  #expanded = false;
11
12
 
12
13
  constructor(private readonly message: BranchSummaryMessage) {
13
- super(1, 1, t => theme.bg("customMessageBg", t));
14
+ super();
15
+ this.addChild(new Spacer(1));
16
+ this.#box = new LeftBorderBox(1, 1, s => theme.fg("dim", s));
17
+ this.addChild(this.#box);
14
18
  this.#updateDisplay();
15
19
  }
16
20
 
@@ -25,21 +29,23 @@ export class BranchSummaryMessageComponent extends Box {
25
29
  }
26
30
 
27
31
  #updateDisplay(): void {
28
- this.clear();
32
+ this.#box.clear();
29
33
 
30
34
  const label = theme.fg("customMessageLabel", theme.bold("[branch]"));
31
- this.addChild(new Text(label, 0, 0));
32
- this.addChild(new Spacer(1));
35
+ this.#box.addChild(new Text(label, 0, 0));
36
+ this.#box.addChild(new Spacer(1));
33
37
 
34
38
  if (this.#expanded) {
35
39
  const header = "**Branch Summary**\n\n";
36
- this.addChild(
40
+ this.#box.addChild(
37
41
  new Markdown(header + this.message.summary, 0, 0, getMarkdownTheme(), {
38
42
  color: (text: string) => theme.fg("customMessageText", text),
39
43
  }),
40
44
  );
41
45
  } else {
42
- this.addChild(new Text(theme.fg("customMessageText", "Branch summary (ctrl+o to expand)"), 0, 0));
46
+ this.#box.addChild(
47
+ new Text(`${theme.fg("customMessageText", "Branch summary")} ${formatClickHint(theme)}`, 0, 0),
48
+ );
43
49
  }
44
50
  }
45
51
  }
@@ -1,16 +1,20 @@
1
- import { Box, Markdown, Spacer, Text } from "@nghyane/arcane-tui";
2
- import { getMarkdownTheme, theme } from "../../modes/theme/theme";
1
+ import { Container, LeftBorderBox, Markdown, Spacer, Text } from "@nghyane/arcane-tui";
3
2
  import type { CompactionSummaryMessage } from "../../session/messages";
3
+ import { getMarkdownTheme, theme } from "../../theme/theme";
4
+ import { formatClickHint } from "../../ui/render-utils";
4
5
 
5
6
  /**
6
7
  * Component that renders a compaction message with collapsed/expanded state.
7
- * Uses same background color as hook messages for visual consistency.
8
8
  */
9
- export class CompactionSummaryMessageComponent extends Box {
9
+ export class CompactionSummaryMessageComponent extends Container {
10
+ #box: LeftBorderBox;
10
11
  #expanded = false;
11
12
 
12
13
  constructor(private readonly message: CompactionSummaryMessage) {
13
- super(1, 1, t => theme.bg("customMessageBg", t));
14
+ super();
15
+ this.addChild(new Spacer(1));
16
+ this.#box = new LeftBorderBox(1, 1, s => theme.fg("dim", s));
17
+ this.addChild(this.#box);
14
18
  this.#updateDisplay();
15
19
  }
16
20
 
@@ -25,26 +29,30 @@ export class CompactionSummaryMessageComponent extends Box {
25
29
  }
26
30
 
27
31
  #updateDisplay(): void {
28
- this.clear();
32
+ this.#box.clear();
29
33
 
30
34
  const tokenStr = this.message.tokensBefore.toLocaleString();
31
35
  const label = theme.fg("customMessageLabel", theme.bold("[compaction]"));
32
- this.addChild(new Text(label, 0, 0));
33
- this.addChild(new Spacer(1));
36
+ this.#box.addChild(new Text(label, 0, 0));
37
+ this.#box.addChild(new Spacer(1));
34
38
 
35
39
  if (this.#expanded) {
36
40
  const header = `**Compacted from ${tokenStr} tokens**\n\n`;
37
- this.addChild(
41
+ this.#box.addChild(
38
42
  new Markdown(header + this.message.summary, 0, 0, getMarkdownTheme(), {
39
43
  color: (text: string) => theme.fg("customMessageText", text),
40
44
  }),
41
45
  );
42
46
  } else {
43
- this.addChild(
44
- new Text(theme.fg("customMessageText", `Compacted from ${tokenStr} tokens (ctrl+o to expand)`), 0, 0),
47
+ this.#box.addChild(
48
+ new Text(
49
+ `${theme.fg("customMessageText", `Compacted from ${tokenStr} tokens`)} ${formatClickHint(theme)}`,
50
+ 0,
51
+ 0,
52
+ ),
45
53
  );
46
54
  if (this.message.shortSummary) {
47
- this.addChild(new Text(theme.fg("customMessageText", this.message.shortSummary), 0, 1));
55
+ this.#box.addChild(new Text(theme.fg("customMessageText", this.message.shortSummary), 0, 1));
48
56
  }
49
57
  }
50
58
  }
@@ -0,0 +1,106 @@
1
+ import type { Component } from "@nghyane/arcane-tui";
2
+ import { Spacer } from "@nghyane/arcane-tui";
3
+ import { theme } from "../../theme/theme";
4
+ import { formatCount, formatStatusIcon } from "../../ui/render-utils";
5
+ import type { ToolExecutionComponent } from "./tool-execution";
6
+
7
+ const CONTEXT_TOOL_LABELS: Record<string, string> = {
8
+ read: "read",
9
+ grep: "grep",
10
+ find: "find",
11
+ fetch: "fetch",
12
+ search_code: "search",
13
+ lsp: "lsp",
14
+ notebook: "notebook",
15
+ };
16
+
17
+ /**
18
+ * Groups consecutive context-gathering tools (read, grep, find, etc.) into
19
+ * a single collapsible summary line to reduce visual noise.
20
+ *
21
+ * Collapsed: "Gathered context 3 reads, 2 greps, 1 find"
22
+ * Expanded: summary + individual tool components
23
+ */
24
+ export class ContextGroupComponent implements Component {
25
+ #entries: Array<{ name: string; component: ToolExecutionComponent }> = [];
26
+ #expanded = false;
27
+ #pendingCount = 0;
28
+ #spacer: Spacer;
29
+
30
+ constructor() {
31
+ this.#spacer = new Spacer(1);
32
+ }
33
+
34
+ addTool(name: string, component: ToolExecutionComponent): void {
35
+ this.#entries.push({ name, component });
36
+ this.#pendingCount++;
37
+ component.setExpanded(this.#expanded);
38
+ }
39
+
40
+ markDone(): void {
41
+ this.#pendingCount = Math.max(0, this.#pendingCount - 1);
42
+ }
43
+
44
+ setExpanded(expanded: boolean): void {
45
+ if (this.#expanded === expanded) return;
46
+ this.#expanded = expanded;
47
+ for (const entry of this.#entries) {
48
+ entry.component.setExpanded(expanded);
49
+ }
50
+ }
51
+
52
+ setMarginTop(lines: number): void {
53
+ this.#spacer.setLines(lines);
54
+ }
55
+
56
+ get size(): number {
57
+ return this.#entries.length;
58
+ }
59
+
60
+ invalidate(): void {
61
+ for (const entry of this.#entries) {
62
+ entry.component.invalidate?.();
63
+ }
64
+ }
65
+
66
+ render(width: number): string[] {
67
+ const lines: string[] = [];
68
+
69
+ // Margin
70
+ lines.push(...this.#spacer.render(width));
71
+
72
+ // Summary line
73
+ const allDone = this.#pendingCount <= 0;
74
+ const icon = allDone ? formatStatusIcon("success", theme) : formatStatusIcon("running", theme);
75
+ const label = allDone ? "Gathered context" : "Gathering context…";
76
+
77
+ // Count by tool type
78
+ const counts = new Map<string, number>();
79
+ for (const entry of this.#entries) {
80
+ const displayName = CONTEXT_TOOL_LABELS[entry.name] ?? entry.name;
81
+ counts.set(displayName, (counts.get(displayName) ?? 0) + 1);
82
+ }
83
+ const parts: string[] = [];
84
+ for (const [name, count] of counts) {
85
+ parts.push(formatCount(name, count));
86
+ }
87
+ const summary = parts.join(", ");
88
+
89
+ const summaryLine = ` ${icon} ${theme.fg("muted", label)} ${theme.fg("dim", summary)}`;
90
+ lines.push(summaryLine);
91
+
92
+ // Always show individual tool entries, indented under summary
93
+ const indent = " ";
94
+ const innerWidth = width - indent.length;
95
+ for (let i = 0; i < this.#entries.length; i++) {
96
+ const entryLines = this.#entries[i].component.render(innerWidth);
97
+ for (const line of entryLines) {
98
+ if (line.trim()) {
99
+ lines.push(indent + line);
100
+ }
101
+ }
102
+ }
103
+
104
+ return lines;
105
+ }
106
+ }
@@ -1,16 +1,16 @@
1
1
  import type { TextContent } from "@nghyane/arcane-ai";
2
2
  import type { Component } from "@nghyane/arcane-tui";
3
- import { Box, Container, Markdown, Spacer, Text } from "@nghyane/arcane-tui";
3
+ import { Container, LeftBorderBox, Markdown, Spacer, Text } from "@nghyane/arcane-tui";
4
4
  import type { MessageRenderer } from "../../extensibility/extensions/types";
5
- import { getMarkdownTheme, theme } from "../../modes/theme/theme";
6
5
  import type { CustomMessage } from "../../session/messages";
6
+ import { getMarkdownTheme, theme } from "../../theme/theme";
7
7
 
8
8
  /**
9
9
  * Component that renders a custom message entry from extensions.
10
10
  * Uses distinct styling to differentiate from user messages.
11
11
  */
12
12
  export class CustomMessageComponent extends Container {
13
- #box: Box;
13
+ #box: LeftBorderBox;
14
14
  #customComponent?: Component;
15
15
  #expanded = false;
16
16
 
@@ -22,8 +22,7 @@ export class CustomMessageComponent extends Container {
22
22
 
23
23
  this.addChild(new Spacer(1));
24
24
 
25
- // Create box with custom background (used for default rendering)
26
- this.#box = new Box(1, 1, t => theme.bg("customMessageBg", t));
25
+ this.#box = new LeftBorderBox(1, 1, s => theme.fg("dim", s));
27
26
 
28
27
  this.#rebuild();
29
28
  }
@@ -1,6 +1,6 @@
1
1
  import * as Diff from "diff";
2
- import { theme } from "../../modes/theme/theme";
3
- import { replaceTabs } from "../../tools/render-utils";
2
+ import { theme } from "../../theme/theme";
3
+ import { replaceTabs } from "../../ui/render-utils";
4
4
 
5
5
  /** SGR dim on / normal intensity — additive, preserves fg/bg colors. */
6
6
  const DIM = "\x1b[2m";
@@ -1,5 +1,5 @@
1
1
  import type { Component } from "@nghyane/arcane-tui";
2
- import { theme } from "../../modes/theme/theme";
2
+ import { theme } from "../../theme/theme";
3
3
 
4
4
  /**
5
5
  * Dynamic border component that adjusts to viewport width.
@@ -23,7 +23,7 @@ import {
23
23
  } from "@nghyane/arcane-tui";
24
24
  import { Settings } from "../../../config/settings";
25
25
  import { DynamicBorder } from "../../../modes/components/dynamic-border";
26
- import { theme } from "../../../modes/theme/theme";
26
+ import { theme } from "../../../theme/theme";
27
27
  import { ExtensionList } from "./extension-list";
28
28
  import { InspectorPanel } from "./inspector-panel";
29
29
  import { applyFilter, createInitialState, filterByProvider, refreshState, toggleProvider } from "./state-manager";
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { type Component, matchesKey, padding, truncateToWidth, visibleWidth } from "@nghyane/arcane-tui";
9
9
  import { isProviderEnabled } from "../../../discovery";
10
- import { theme } from "../../../modes/theme/theme";
10
+ import { theme } from "../../../theme/theme";
11
11
  import { applyFilter } from "./state-manager";
12
12
  import type { Extension, ExtensionKind, ExtensionState } from "./types";
13
13