@renxqoo/renx-code 0.0.3 → 0.0.4

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 (306) hide show
  1. package/README.md +58 -223
  2. package/bin/renx.cjs +34 -0
  3. package/package.json +27 -83
  4. package/src/App.tsx +297 -0
  5. package/src/agent/runtime/event-format.ts +258 -0
  6. package/src/agent/runtime/model-types.ts +13 -0
  7. package/src/agent/runtime/runtime.context-usage.test.ts +193 -0
  8. package/src/agent/runtime/runtime.error-handling.test.ts +236 -0
  9. package/src/agent/runtime/runtime.simple.test.ts +16 -0
  10. package/src/agent/runtime/runtime.test.ts +293 -0
  11. package/src/agent/runtime/runtime.ts +881 -0
  12. package/src/agent/runtime/runtime.usage-forwarding.test.ts +229 -0
  13. package/src/agent/runtime/source-modules.test.ts +57 -0
  14. package/src/agent/runtime/source-modules.ts +353 -0
  15. package/src/agent/runtime/tool-call-buffer.test.ts +65 -0
  16. package/src/agent/runtime/tool-call-buffer.ts +60 -0
  17. package/src/agent/runtime/tool-confirmation.test.ts +56 -0
  18. package/src/agent/runtime/tool-confirmation.ts +15 -0
  19. package/src/agent/runtime/types.ts +99 -0
  20. package/src/commands/slash-commands.test.ts +216 -0
  21. package/src/commands/slash-commands.ts +64 -0
  22. package/src/components/chat/assistant-reply.test.tsx +47 -0
  23. package/src/components/chat/assistant-reply.tsx +136 -0
  24. package/src/components/chat/assistant-segment.test.ts +99 -0
  25. package/src/components/chat/assistant-segment.tsx +125 -0
  26. package/src/components/chat/assistant-tool-group.tsx +900 -0
  27. package/src/components/chat/code-block.test.tsx +206 -0
  28. package/src/components/chat/code-block.tsx +313 -0
  29. package/src/components/chat/prompt-card.tsx +81 -0
  30. package/src/components/chat/segment-groups.test.ts +52 -0
  31. package/src/components/chat/segment-groups.ts +106 -0
  32. package/src/components/chat/turn-item.tsx +39 -0
  33. package/src/components/conversation-panel.tsx +43 -0
  34. package/src/components/file-mention-menu.tsx +77 -0
  35. package/src/components/file-picker-dialog.tsx +206 -0
  36. package/src/components/footer-hints.tsx +75 -0
  37. package/src/components/model-picker-dialog.tsx +248 -0
  38. package/src/components/prompt.tsx +233 -0
  39. package/src/components/slash-command-menu.tsx +65 -0
  40. package/src/components/tool-confirm-dialog-content.test.ts +103 -0
  41. package/src/components/tool-confirm-dialog-content.ts +186 -0
  42. package/src/components/tool-confirm-dialog.tsx +187 -0
  43. package/src/components/tool-display-config.ts +119 -0
  44. package/src/context-usage-regressions.test.ts +26 -0
  45. package/src/files/attachment-capabilities.test.ts +30 -0
  46. package/src/files/attachment-capabilities.ts +50 -0
  47. package/src/files/attachment-content.ts +153 -0
  48. package/src/files/file-mention-query.test.ts +34 -0
  49. package/src/files/file-mention-query.ts +32 -0
  50. package/src/files/prompt-display.ts +13 -0
  51. package/src/files/types.ts +5 -0
  52. package/src/files/workspace-files.ts +63 -0
  53. package/src/hooks/agent-event-handlers.test.ts +207 -0
  54. package/src/hooks/agent-event-handlers.ts +196 -0
  55. package/src/hooks/chat-local-replies.fixed.test.ts +119 -0
  56. package/src/hooks/chat-local-replies.test.ts +153 -0
  57. package/src/hooks/chat-local-replies.ts +63 -0
  58. package/src/hooks/turn-updater.test.ts +70 -0
  59. package/src/hooks/turn-updater.ts +166 -0
  60. package/src/hooks/use-agent-chat.context.test.ts +10 -0
  61. package/src/hooks/use-agent-chat.status.test.ts +14 -0
  62. package/src/hooks/use-agent-chat.test.ts +80 -0
  63. package/src/hooks/use-agent-chat.ts +621 -0
  64. package/src/hooks/use-file-mention-menu.ts +196 -0
  65. package/src/hooks/use-file-picker.ts +185 -0
  66. package/src/hooks/use-model-picker.ts +196 -0
  67. package/src/hooks/use-slash-command-menu.ts +154 -0
  68. package/src/index.tsx +55 -0
  69. package/src/runtime/clipboard.test.ts +43 -0
  70. package/src/runtime/clipboard.ts +89 -0
  71. package/src/runtime/exit.test.ts +177 -0
  72. package/src/runtime/exit.ts +98 -0
  73. package/src/runtime/runtime-support.test.ts +31 -0
  74. package/src/runtime/terminal-theme.test.ts +55 -0
  75. package/src/runtime/terminal-theme.ts +196 -0
  76. package/src/types/chat.ts +32 -0
  77. package/src/types/message-content.ts +48 -0
  78. package/src/ui/open-code-theme.ts +176 -0
  79. package/src/ui/opencode-markdown.ts +211 -0
  80. package/src/ui/theme.simple.test.ts +52 -0
  81. package/src/ui/theme.test.ts +151 -0
  82. package/src/ui/theme.ts +152 -0
  83. package/src/utils/time.test.ts +144 -0
  84. package/src/utils/time.ts +7 -0
  85. package/tsconfig.json +30 -0
  86. package/LICENSE +0 -21
  87. package/dist/App.d.ts +0 -2
  88. package/dist/App.d.ts.map +0 -1
  89. package/dist/App.js +0 -170
  90. package/dist/App.js.map +0 -1
  91. package/dist/agent/prompts/system.d.ts +0 -24
  92. package/dist/agent/prompts/system.d.ts.map +0 -1
  93. package/dist/agent/prompts/system.js +0 -222
  94. package/dist/agent/prompts/system.js.map +0 -1
  95. package/dist/agent/runtime/event-format.d.ts +0 -17
  96. package/dist/agent/runtime/event-format.d.ts.map +0 -1
  97. package/dist/agent/runtime/event-format.js +0 -194
  98. package/dist/agent/runtime/event-format.js.map +0 -1
  99. package/dist/agent/runtime/model-types.d.ts +0 -13
  100. package/dist/agent/runtime/model-types.d.ts.map +0 -1
  101. package/dist/agent/runtime/model-types.js +0 -1
  102. package/dist/agent/runtime/model-types.js.map +0 -1
  103. package/dist/agent/runtime/runtime.d.ts +0 -16
  104. package/dist/agent/runtime/runtime.d.ts.map +0 -1
  105. package/dist/agent/runtime/runtime.js +0 -691
  106. package/dist/agent/runtime/runtime.js.map +0 -1
  107. package/dist/agent/runtime/source-modules.d.ts +0 -176
  108. package/dist/agent/runtime/source-modules.d.ts.map +0 -1
  109. package/dist/agent/runtime/source-modules.js +0 -110
  110. package/dist/agent/runtime/source-modules.js.map +0 -1
  111. package/dist/agent/runtime/tool-call-buffer.d.ts +0 -12
  112. package/dist/agent/runtime/tool-call-buffer.d.ts.map +0 -1
  113. package/dist/agent/runtime/tool-call-buffer.js +0 -48
  114. package/dist/agent/runtime/tool-call-buffer.js.map +0 -1
  115. package/dist/agent/runtime/tool-confirmation.d.ts +0 -3
  116. package/dist/agent/runtime/tool-confirmation.d.ts.map +0 -1
  117. package/dist/agent/runtime/tool-confirmation.js +0 -9
  118. package/dist/agent/runtime/tool-confirmation.js.map +0 -1
  119. package/dist/agent/runtime/types.d.ts +0 -86
  120. package/dist/agent/runtime/types.d.ts.map +0 -1
  121. package/dist/agent/runtime/types.js +0 -1
  122. package/dist/agent/runtime/types.js.map +0 -1
  123. package/dist/cli.d.ts +0 -3
  124. package/dist/cli.d.ts.map +0 -1
  125. package/dist/cli.js +0 -12
  126. package/dist/cli.js.map +0 -1
  127. package/dist/commands/slash-commands.d.ts +0 -11
  128. package/dist/commands/slash-commands.d.ts.map +0 -1
  129. package/dist/commands/slash-commands.js +0 -48
  130. package/dist/commands/slash-commands.js.map +0 -1
  131. package/dist/components/chat/assistant-reply.d.ts +0 -13
  132. package/dist/components/chat/assistant-reply.d.ts.map +0 -1
  133. package/dist/components/chat/assistant-reply.js +0 -78
  134. package/dist/components/chat/assistant-reply.js.map +0 -1
  135. package/dist/components/chat/assistant-segment.d.ts +0 -8
  136. package/dist/components/chat/assistant-segment.d.ts.map +0 -1
  137. package/dist/components/chat/assistant-segment.js +0 -54
  138. package/dist/components/chat/assistant-segment.js.map +0 -1
  139. package/dist/components/chat/assistant-tool-group.d.ts +0 -7
  140. package/dist/components/chat/assistant-tool-group.d.ts.map +0 -1
  141. package/dist/components/chat/assistant-tool-group.js +0 -695
  142. package/dist/components/chat/assistant-tool-group.js.map +0 -1
  143. package/dist/components/chat/code-block.d.ts +0 -16
  144. package/dist/components/chat/code-block.d.ts.map +0 -1
  145. package/dist/components/chat/code-block.js +0 -194
  146. package/dist/components/chat/code-block.js.map +0 -1
  147. package/dist/components/chat/prompt-card.d.ts +0 -9
  148. package/dist/components/chat/prompt-card.d.ts.map +0 -1
  149. package/dist/components/chat/prompt-card.js +0 -18
  150. package/dist/components/chat/prompt-card.js.map +0 -1
  151. package/dist/components/chat/segment-groups.d.ts +0 -24
  152. package/dist/components/chat/segment-groups.d.ts.map +0 -1
  153. package/dist/components/chat/segment-groups.js +0 -69
  154. package/dist/components/chat/segment-groups.js.map +0 -1
  155. package/dist/components/chat/turn-item.d.ts +0 -9
  156. package/dist/components/chat/turn-item.d.ts.map +0 -1
  157. package/dist/components/chat/turn-item.js +0 -11
  158. package/dist/components/chat/turn-item.js.map +0 -1
  159. package/dist/components/conversation-panel.d.ts +0 -8
  160. package/dist/components/conversation-panel.d.ts.map +0 -1
  161. package/dist/components/conversation-panel.js +0 -8
  162. package/dist/components/conversation-panel.js.map +0 -1
  163. package/dist/components/file-mention-menu.d.ts +0 -11
  164. package/dist/components/file-mention-menu.d.ts.map +0 -1
  165. package/dist/components/file-mention-menu.js +0 -15
  166. package/dist/components/file-mention-menu.js.map +0 -1
  167. package/dist/components/file-picker-dialog.d.ts +0 -21
  168. package/dist/components/file-picker-dialog.d.ts.map +0 -1
  169. package/dist/components/file-picker-dialog.js +0 -48
  170. package/dist/components/file-picker-dialog.js.map +0 -1
  171. package/dist/components/footer-hints.d.ts +0 -7
  172. package/dist/components/footer-hints.d.ts.map +0 -1
  173. package/dist/components/footer-hints.js +0 -29
  174. package/dist/components/footer-hints.js.map +0 -1
  175. package/dist/components/model-picker-dialog.d.ts +0 -20
  176. package/dist/components/model-picker-dialog.d.ts.map +0 -1
  177. package/dist/components/model-picker-dialog.js +0 -72
  178. package/dist/components/model-picker-dialog.js.map +0 -1
  179. package/dist/components/prompt.d.ts +0 -18
  180. package/dist/components/prompt.d.ts.map +0 -1
  181. package/dist/components/prompt.js +0 -96
  182. package/dist/components/prompt.js.map +0 -1
  183. package/dist/components/slash-command-menu.d.ts +0 -9
  184. package/dist/components/slash-command-menu.d.ts.map +0 -1
  185. package/dist/components/slash-command-menu.js +0 -20
  186. package/dist/components/slash-command-menu.js.map +0 -1
  187. package/dist/components/tool-confirm-dialog-content.d.ts +0 -15
  188. package/dist/components/tool-confirm-dialog-content.d.ts.map +0 -1
  189. package/dist/components/tool-confirm-dialog-content.js +0 -143
  190. package/dist/components/tool-confirm-dialog-content.js.map +0 -1
  191. package/dist/components/tool-confirm-dialog.d.ts +0 -12
  192. package/dist/components/tool-confirm-dialog.d.ts.map +0 -1
  193. package/dist/components/tool-confirm-dialog.js +0 -21
  194. package/dist/components/tool-confirm-dialog.js.map +0 -1
  195. package/dist/components/tool-display-config.d.ts +0 -11
  196. package/dist/components/tool-display-config.d.ts.map +0 -1
  197. package/dist/components/tool-display-config.js +0 -94
  198. package/dist/components/tool-display-config.js.map +0 -1
  199. package/dist/config/paths.d.ts +0 -7
  200. package/dist/config/paths.d.ts.map +0 -1
  201. package/dist/config/paths.js +0 -24
  202. package/dist/config/paths.js.map +0 -1
  203. package/dist/files/attachment-capabilities.d.ts +0 -19
  204. package/dist/files/attachment-capabilities.d.ts.map +0 -1
  205. package/dist/files/attachment-capabilities.js +0 -26
  206. package/dist/files/attachment-capabilities.js.map +0 -1
  207. package/dist/files/attachment-content.d.ts +0 -5
  208. package/dist/files/attachment-content.d.ts.map +0 -1
  209. package/dist/files/attachment-content.js +0 -117
  210. package/dist/files/attachment-content.js.map +0 -1
  211. package/dist/files/file-mention-query.d.ts +0 -9
  212. package/dist/files/file-mention-query.d.ts.map +0 -1
  213. package/dist/files/file-mention-query.js +0 -23
  214. package/dist/files/file-mention-query.js.map +0 -1
  215. package/dist/files/prompt-display.d.ts +0 -3
  216. package/dist/files/prompt-display.d.ts.map +0 -1
  217. package/dist/files/prompt-display.js +0 -11
  218. package/dist/files/prompt-display.js.map +0 -1
  219. package/dist/files/types.d.ts +0 -6
  220. package/dist/files/types.d.ts.map +0 -1
  221. package/dist/files/types.js +0 -1
  222. package/dist/files/types.js.map +0 -1
  223. package/dist/files/workspace-files.d.ts +0 -3
  224. package/dist/files/workspace-files.d.ts.map +0 -1
  225. package/dist/files/workspace-files.js +0 -48
  226. package/dist/files/workspace-files.js.map +0 -1
  227. package/dist/hooks/agent-event-handlers.d.ts +0 -11
  228. package/dist/hooks/agent-event-handlers.d.ts.map +0 -1
  229. package/dist/hooks/agent-event-handlers.js +0 -137
  230. package/dist/hooks/agent-event-handlers.js.map +0 -1
  231. package/dist/hooks/chat-local-replies.d.ts +0 -9
  232. package/dist/hooks/chat-local-replies.d.ts.map +0 -1
  233. package/dist/hooks/chat-local-replies.js +0 -54
  234. package/dist/hooks/chat-local-replies.js.map +0 -1
  235. package/dist/hooks/turn-updater.d.ts +0 -9
  236. package/dist/hooks/turn-updater.d.ts.map +0 -1
  237. package/dist/hooks/turn-updater.js +0 -103
  238. package/dist/hooks/turn-updater.js.map +0 -1
  239. package/dist/hooks/use-agent-chat.d.ts +0 -29
  240. package/dist/hooks/use-agent-chat.d.ts.map +0 -1
  241. package/dist/hooks/use-agent-chat.js +0 -455
  242. package/dist/hooks/use-agent-chat.js.map +0 -1
  243. package/dist/hooks/use-file-mention-menu.d.ts +0 -22
  244. package/dist/hooks/use-file-mention-menu.d.ts.map +0 -1
  245. package/dist/hooks/use-file-mention-menu.js +0 -137
  246. package/dist/hooks/use-file-mention-menu.js.map +0 -1
  247. package/dist/hooks/use-file-picker.d.ts +0 -21
  248. package/dist/hooks/use-file-picker.d.ts.map +0 -1
  249. package/dist/hooks/use-file-picker.js +0 -145
  250. package/dist/hooks/use-file-picker.js.map +0 -1
  251. package/dist/hooks/use-model-picker.d.ts +0 -23
  252. package/dist/hooks/use-model-picker.d.ts.map +0 -1
  253. package/dist/hooks/use-model-picker.js +0 -151
  254. package/dist/hooks/use-model-picker.js.map +0 -1
  255. package/dist/hooks/use-slash-command-menu.d.ts +0 -19
  256. package/dist/hooks/use-slash-command-menu.d.ts.map +0 -1
  257. package/dist/hooks/use-slash-command-menu.js +0 -101
  258. package/dist/hooks/use-slash-command-menu.js.map +0 -1
  259. package/dist/index.d.ts +0 -2
  260. package/dist/index.d.ts.map +0 -1
  261. package/dist/index.js +0 -6
  262. package/dist/index.js.map +0 -1
  263. package/dist/run-cli-app.d.ts +0 -2
  264. package/dist/run-cli-app.d.ts.map +0 -1
  265. package/dist/run-cli-app.js +0 -41
  266. package/dist/run-cli-app.js.map +0 -1
  267. package/dist/runtime/clipboard.d.ts +0 -10
  268. package/dist/runtime/clipboard.d.ts.map +0 -1
  269. package/dist/runtime/clipboard.js +0 -64
  270. package/dist/runtime/clipboard.js.map +0 -1
  271. package/dist/runtime/exit.d.ts +0 -7
  272. package/dist/runtime/exit.d.ts.map +0 -1
  273. package/dist/runtime/exit.js +0 -85
  274. package/dist/runtime/exit.js.map +0 -1
  275. package/dist/runtime/runtime-support.d.ts +0 -4
  276. package/dist/runtime/runtime-support.d.ts.map +0 -1
  277. package/dist/runtime/runtime-support.js +0 -19
  278. package/dist/runtime/runtime-support.js.map +0 -1
  279. package/dist/runtime/terminal-theme.d.ts +0 -25
  280. package/dist/runtime/terminal-theme.d.ts.map +0 -1
  281. package/dist/runtime/terminal-theme.js +0 -148
  282. package/dist/runtime/terminal-theme.js.map +0 -1
  283. package/dist/types/chat.d.ts +0 -29
  284. package/dist/types/chat.d.ts.map +0 -1
  285. package/dist/types/chat.js +0 -1
  286. package/dist/types/chat.js.map +0 -1
  287. package/dist/types/message-content.d.ts +0 -38
  288. package/dist/types/message-content.d.ts.map +0 -1
  289. package/dist/types/message-content.js +0 -1
  290. package/dist/types/message-content.js.map +0 -1
  291. package/dist/ui/open-code-theme.d.ts +0 -58
  292. package/dist/ui/open-code-theme.d.ts.map +0 -1
  293. package/dist/ui/open-code-theme.js +0 -113
  294. package/dist/ui/open-code-theme.js.map +0 -1
  295. package/dist/ui/opencode-markdown.d.ts +0 -7
  296. package/dist/ui/opencode-markdown.d.ts.map +0 -1
  297. package/dist/ui/opencode-markdown.js +0 -169
  298. package/dist/ui/opencode-markdown.js.map +0 -1
  299. package/dist/ui/theme.d.ts +0 -68
  300. package/dist/ui/theme.d.ts.map +0 -1
  301. package/dist/ui/theme.js +0 -80
  302. package/dist/ui/theme.js.map +0 -1
  303. package/dist/utils/time.d.ts +0 -2
  304. package/dist/utils/time.d.ts.map +0 -1
  305. package/dist/utils/time.js +0 -7
  306. package/dist/utils/time.js.map +0 -1
@@ -0,0 +1,186 @@
1
+ import type { AgentToolConfirmEvent } from '../agent/runtime/types';
2
+ import { getToolHiddenArgumentKeys, getToolDisplayName } from './tool-display-config';
3
+
4
+ export type ToolConfirmDialogContent = {
5
+ summary: string;
6
+ detail?: string;
7
+ reason?: string;
8
+ requestedPath?: string;
9
+ allowedDirectories: string[];
10
+ argumentItems: Array<{
11
+ label: string;
12
+ value: string;
13
+ multiline?: boolean;
14
+ }>;
15
+ };
16
+
17
+ const asRecord = (value: unknown): Record<string, unknown> => {
18
+ return value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
19
+ };
20
+
21
+ const readString = (value: unknown): string | undefined => {
22
+ return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
23
+ };
24
+
25
+ const readStringArray = (value: unknown): string[] => {
26
+ return Array.isArray(value)
27
+ ? value.filter((item): item is string => typeof item === 'string')
28
+ : [];
29
+ };
30
+
31
+ const stringifyPretty = (value: unknown): string | undefined => {
32
+ if (value === undefined) {
33
+ return undefined;
34
+ }
35
+ try {
36
+ return JSON.stringify(value, null, 2);
37
+ } catch {
38
+ return String(value);
39
+ }
40
+ };
41
+
42
+ const formatPathTarget = (value: unknown, fallback = '.'): string => {
43
+ return readString(value) ?? fallback;
44
+ };
45
+
46
+ const parseJsonLike = (value: unknown): unknown => {
47
+ if (typeof value !== 'string') {
48
+ return value;
49
+ }
50
+
51
+ const trimmed = value.trim();
52
+ if (
53
+ trimmed.length < 2 ||
54
+ !(
55
+ (trimmed.startsWith('{') && trimmed.endsWith('}')) ||
56
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))
57
+ )
58
+ ) {
59
+ return value;
60
+ }
61
+
62
+ try {
63
+ return JSON.parse(trimmed);
64
+ } catch {
65
+ return value;
66
+ }
67
+ };
68
+
69
+ const humanizeKey = (key: string): string => {
70
+ return key
71
+ .split('_')
72
+ .filter(Boolean)
73
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
74
+ .join(' ');
75
+ };
76
+
77
+ const formatArgumentValue = (
78
+ value: unknown
79
+ ): { value: string; multiline?: boolean } | undefined => {
80
+ const normalized = parseJsonLike(value);
81
+
82
+ if (typeof normalized === 'string') {
83
+ return normalized.trim().length > 0
84
+ ? {
85
+ value: normalized,
86
+ multiline: normalized.includes('\n'),
87
+ }
88
+ : undefined;
89
+ }
90
+
91
+ if (typeof normalized === 'number' || typeof normalized === 'boolean') {
92
+ return { value: String(normalized) };
93
+ }
94
+
95
+ const pretty = stringifyPretty(normalized);
96
+ if (!pretty) {
97
+ return undefined;
98
+ }
99
+
100
+ return {
101
+ value: pretty,
102
+ multiline: true,
103
+ };
104
+ };
105
+
106
+ const buildArgumentItems = (
107
+ event: AgentToolConfirmEvent
108
+ ): ToolConfirmDialogContent['argumentItems'] => {
109
+ const hiddenKeys = new Set(getToolHiddenArgumentKeys(event.toolName));
110
+
111
+ return Object.entries(asRecord(event.args)).flatMap(([key, value]) => {
112
+ if (hiddenKeys.has(key)) {
113
+ return [];
114
+ }
115
+
116
+ const formatted = formatArgumentValue(value);
117
+ if (!formatted) {
118
+ return [];
119
+ }
120
+
121
+ return [
122
+ {
123
+ label: humanizeKey(key),
124
+ value: formatted.value,
125
+ multiline: formatted.multiline,
126
+ },
127
+ ];
128
+ });
129
+ };
130
+
131
+ const buildSummary = (event: AgentToolConfirmEvent): { summary: string; detail?: string } => {
132
+ const args = asRecord(event.args);
133
+
134
+ switch (event.toolName) {
135
+ case 'bash': {
136
+ const command = readString(args.command) ?? '(empty command)';
137
+ const description = readString(args.description);
138
+ return {
139
+ summary: description ? `Run bash: ${description}` : 'Run bash command',
140
+ detail: `$ ${command}`,
141
+ };
142
+ }
143
+ case 'file_read':
144
+ return { summary: `Read ${formatPathTarget(args.path)}` };
145
+ case 'file_edit':
146
+ return { summary: `Edit ${formatPathTarget(args.path)}` };
147
+ case 'write_file':
148
+ return { summary: `Write ${formatPathTarget(args.path)}` };
149
+ case 'glob':
150
+ return {
151
+ summary: `Glob ${readString(args.pattern) ?? '*'}`,
152
+ detail: `Path: ${formatPathTarget(args.path)}`,
153
+ };
154
+ case 'grep':
155
+ return {
156
+ summary: `Grep ${readString(args.pattern) ?? ''}`,
157
+ detail: `Path: ${formatPathTarget(args.path)}`,
158
+ };
159
+ case 'task':
160
+ case 'agent': {
161
+ const displayName = getToolDisplayName(event.toolName).replace(/\s+run$/i, '');
162
+ return {
163
+ summary: `Run ${displayName} ${(readString(args.subagent_type) ?? 'agent').trim()}`,
164
+ detail: readString(args.description),
165
+ };
166
+ }
167
+ default:
168
+ return { summary: `Call ${event.toolName}` };
169
+ }
170
+ };
171
+
172
+ export const buildToolConfirmDialogContent = (
173
+ event: AgentToolConfirmEvent
174
+ ): ToolConfirmDialogContent => {
175
+ const metadata = asRecord(event.metadata);
176
+ const { summary, detail } = buildSummary(event);
177
+
178
+ return {
179
+ summary,
180
+ detail,
181
+ reason: readString(event.reason),
182
+ requestedPath: readString(metadata.requestedPath),
183
+ allowedDirectories: readStringArray(metadata.allowedDirectories),
184
+ argumentItems: buildArgumentItems(event),
185
+ };
186
+ };
@@ -0,0 +1,187 @@
1
+ import { TextAttributes } from '@opentui/core';
2
+
3
+ import type { AgentToolConfirmEvent } from '../agent/runtime/types';
4
+ import { uiTheme } from '../ui/theme';
5
+ import { buildToolConfirmDialogContent } from './tool-confirm-dialog-content';
6
+
7
+ type ToolConfirmDialogProps = {
8
+ visible: boolean;
9
+ viewportWidth: number;
10
+ viewportHeight: number;
11
+ request: (AgentToolConfirmEvent & { selectedAction: 'approve' | 'deny' }) | null;
12
+ };
13
+
14
+ const selectedForeground = '#050608';
15
+
16
+ const renderButton = (label: string, selected: boolean) => {
17
+ return (
18
+ <box
19
+ paddingLeft={1}
20
+ paddingRight={1}
21
+ backgroundColor={selected ? uiTheme.accent : uiTheme.surface}
22
+ border={['top', 'bottom', 'left', 'right']}
23
+ borderColor={selected ? uiTheme.accent : uiTheme.divider}
24
+ >
25
+ <text fg={selected ? selectedForeground : uiTheme.text} attributes={TextAttributes.BOLD}>
26
+ {label}
27
+ </text>
28
+ </box>
29
+ );
30
+ };
31
+
32
+ export const ToolConfirmDialog = ({
33
+ visible,
34
+ viewportWidth,
35
+ viewportHeight,
36
+ request,
37
+ }: ToolConfirmDialogProps) => {
38
+ if (!visible || !request) {
39
+ return null;
40
+ }
41
+
42
+ const content = buildToolConfirmDialogContent(request);
43
+ const panelWidth = Math.min(86, Math.max(48, viewportWidth - 8));
44
+ const panelHeight = Math.min(22, Math.max(14, viewportHeight - 6));
45
+ const left = Math.max(2, Math.floor((viewportWidth - panelWidth) / 2));
46
+ const top = Math.max(1, Math.floor((viewportHeight - panelHeight) / 2));
47
+ const selectedAction = request.selectedAction;
48
+
49
+ return (
50
+ <box
51
+ position="absolute"
52
+ top={top}
53
+ left={left}
54
+ width={panelWidth}
55
+ height={panelHeight}
56
+ zIndex={150}
57
+ >
58
+ <box
59
+ width="100%"
60
+ height="100%"
61
+ flexDirection="column"
62
+ backgroundColor={uiTheme.surface}
63
+ border={['top', 'bottom', 'left', 'right']}
64
+ borderColor={uiTheme.divider}
65
+ >
66
+ <box
67
+ gap={1}
68
+ paddingLeft={2}
69
+ paddingRight={2}
70
+ paddingTop={1}
71
+ paddingBottom={1}
72
+ flexDirection="column"
73
+ >
74
+ <box flexDirection="row" gap={1}>
75
+ <text fg={uiTheme.accent} attributes={TextAttributes.BOLD}>
76
+ {'△'}
77
+ </text>
78
+ <text fg={uiTheme.text} attributes={TextAttributes.BOLD}>
79
+ Permission required
80
+ </text>
81
+ </box>
82
+
83
+ <box paddingLeft={1}>
84
+ <text fg={uiTheme.text} attributes={TextAttributes.BOLD}>
85
+ {content.summary}
86
+ </text>
87
+ </box>
88
+
89
+ {content.detail ? (
90
+ <box paddingLeft={1}>
91
+ <text fg={uiTheme.text}>{content.detail}</text>
92
+ </box>
93
+ ) : null}
94
+
95
+ {content.reason ? (
96
+ <box paddingLeft={1} flexDirection="column">
97
+ <text fg={uiTheme.muted}>Reason</text>
98
+ <text fg={uiTheme.text} wrapMode="word">
99
+ {content.reason}
100
+ </text>
101
+ </box>
102
+ ) : null}
103
+
104
+ {content.requestedPath ? (
105
+ <box paddingLeft={1} flexDirection="column">
106
+ <text fg={uiTheme.muted}>Requested path</text>
107
+ <text fg={uiTheme.text} wrapMode="word">
108
+ {content.requestedPath}
109
+ </text>
110
+ </box>
111
+ ) : null}
112
+
113
+ {content.allowedDirectories.length > 0 ? (
114
+ <box paddingLeft={1} flexDirection="column">
115
+ <text fg={uiTheme.muted}>Allowed directories</text>
116
+ {content.allowedDirectories.map(directory => (
117
+ <text key={directory} fg={uiTheme.text} wrapMode="word">
118
+ {directory}
119
+ </text>
120
+ ))}
121
+ </box>
122
+ ) : null}
123
+
124
+ {content.argumentItems.length > 0 ? (
125
+ <box flexGrow={1} paddingLeft={1} paddingRight={1}>
126
+ <scrollbox
127
+ height="100%"
128
+ scrollY
129
+ stickyScroll
130
+ scrollbarOptions={{ visible: false }}
131
+ viewportOptions={{ backgroundColor: uiTheme.panel }}
132
+ contentOptions={{ backgroundColor: uiTheme.panel }}
133
+ >
134
+ <box
135
+ backgroundColor={uiTheme.panel}
136
+ paddingX={1}
137
+ paddingY={1}
138
+ gap={1}
139
+ flexDirection="column"
140
+ >
141
+ <text fg={uiTheme.muted}>Arguments</text>
142
+ {content.argumentItems.map((item, index) => (
143
+ <box key={`${item.label}:${index}`} flexDirection="column">
144
+ <text fg={uiTheme.muted}>{item.label}</text>
145
+ <text
146
+ fg={uiTheme.text}
147
+ wrapMode={item.multiline ? 'char' : 'word'}
148
+ attributes={item.multiline ? uiTheme.typography.code : undefined}
149
+ >
150
+ {item.value}
151
+ </text>
152
+ </box>
153
+ ))}
154
+ </box>
155
+ </scrollbox>
156
+ </box>
157
+ ) : null}
158
+ </box>
159
+
160
+ <box
161
+ flexDirection="row"
162
+ justifyContent="space-between"
163
+ paddingLeft={2}
164
+ paddingRight={2}
165
+ paddingTop={1}
166
+ paddingBottom={1}
167
+ backgroundColor={uiTheme.panel}
168
+ >
169
+ <box flexDirection="row" gap={1}>
170
+ {renderButton('Allow once', selectedAction === 'approve')}
171
+ {renderButton('Reject', selectedAction === 'deny')}
172
+ </box>
173
+ <text fg={uiTheme.muted}>left/right select enter confirm esc reject</text>
174
+ </box>
175
+
176
+ <box
177
+ position="absolute"
178
+ top={0}
179
+ left={0}
180
+ width={1}
181
+ height="100%"
182
+ backgroundColor={uiTheme.accent}
183
+ />
184
+ </box>
185
+ </box>
186
+ );
187
+ };
@@ -0,0 +1,119 @@
1
+ export type ToolDisplayConfig = {
2
+ aliases?: string[];
3
+ displayName?: string;
4
+ icon?: string;
5
+ hiddenArgumentKeys?: string[];
6
+ };
7
+
8
+ const TOOL_DISPLAY_CONFIG: Record<string, ToolDisplayConfig> = {
9
+ agent: {
10
+ aliases: ['task'],
11
+ displayName: 'task run',
12
+ icon: '◉',
13
+ hiddenArgumentKeys: ['subagent_type', 'description'],
14
+ },
15
+ task: {
16
+ displayName: 'task run',
17
+ icon: '◉',
18
+ hiddenArgumentKeys: ['subagent_type', 'description'],
19
+ },
20
+ bash: {
21
+ icon: '$',
22
+ hiddenArgumentKeys: ['command', 'description'],
23
+ },
24
+ file_read: {
25
+ icon: '→',
26
+ hiddenArgumentKeys: ['path'],
27
+ },
28
+ file_edit: {
29
+ icon: '←',
30
+ hiddenArgumentKeys: ['path'],
31
+ },
32
+ write_file: {
33
+ icon: '←',
34
+ hiddenArgumentKeys: ['path'],
35
+ },
36
+ glob: {
37
+ icon: '✱',
38
+ hiddenArgumentKeys: ['pattern', 'path'],
39
+ },
40
+ grep: {
41
+ icon: '✱',
42
+ hiddenArgumentKeys: ['pattern', 'path'],
43
+ },
44
+ webfetch: {
45
+ icon: '%',
46
+ },
47
+ };
48
+
49
+ const TOOL_NAME_PREFIX_DISPLAY: Array<{ prefix: string; displayPrefix: string; icon?: string }> = [
50
+ {
51
+ prefix: 'task_',
52
+ displayPrefix: 'task ',
53
+ icon: '◉',
54
+ },
55
+ ];
56
+
57
+ export function getToolDisplayConfig(toolName: string): ToolDisplayConfig {
58
+ return TOOL_DISPLAY_CONFIG[toolName] ?? {};
59
+ }
60
+
61
+ export function getToolDisplayName(toolName: string): string {
62
+ const direct = getToolDisplayConfig(toolName).displayName;
63
+ if (direct) {
64
+ return direct;
65
+ }
66
+
67
+ for (const entry of TOOL_NAME_PREFIX_DISPLAY) {
68
+ if (toolName.startsWith(entry.prefix)) {
69
+ return toolName.replace(entry.prefix, entry.displayPrefix).replace(/_/g, ' ');
70
+ }
71
+ }
72
+
73
+ const aliased = Object.entries(TOOL_DISPLAY_CONFIG).find(([, config]) =>
74
+ (config.aliases ?? []).includes(toolName)
75
+ );
76
+ if (aliased?.[1].displayName) {
77
+ return aliased[1].displayName as string;
78
+ }
79
+
80
+ return toolName;
81
+ }
82
+
83
+ export function getToolDisplayIcon(toolName: string): string {
84
+ const direct = getToolDisplayConfig(toolName).icon;
85
+ if (direct) {
86
+ return direct;
87
+ }
88
+
89
+ for (const entry of TOOL_NAME_PREFIX_DISPLAY) {
90
+ if (toolName.startsWith(entry.prefix) && entry.icon) {
91
+ return entry.icon;
92
+ }
93
+ }
94
+
95
+ const aliased = Object.entries(TOOL_DISPLAY_CONFIG).find(([, config]) =>
96
+ (config.aliases ?? []).includes(toolName)
97
+ );
98
+ if (aliased?.[1].icon) {
99
+ return aliased[1].icon as string;
100
+ }
101
+
102
+ return '⚙';
103
+ }
104
+
105
+ export function getToolHiddenArgumentKeys(toolName: string): string[] {
106
+ const direct = getToolDisplayConfig(toolName).hiddenArgumentKeys;
107
+ if (direct) {
108
+ return [...direct];
109
+ }
110
+
111
+ const aliased = Object.entries(TOOL_DISPLAY_CONFIG).find(([, config]) =>
112
+ (config.aliases ?? []).includes(toolName)
113
+ );
114
+ if (aliased?.[1].hiddenArgumentKeys) {
115
+ return [...(aliased[1].hiddenArgumentKeys as string[])];
116
+ }
117
+
118
+ return [];
119
+ }
@@ -0,0 +1,26 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, join } from 'node:path';
4
+
5
+ import { describe, expect, it } from 'vitest';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ const readSource = async (path: string) => {
11
+ return readFile(path, 'utf8');
12
+ };
13
+
14
+ describe('context usage regressions', () => {
15
+ it('does not clear context usage at the start of a new request', async () => {
16
+ const source = await readSource(join(__dirname, 'hooks/use-agent-chat.ts'));
17
+
18
+ expect(source).not.toContain('setContextUsagePercent(null);');
19
+ });
20
+
21
+ it('does not render missing context usage as 0%', async () => {
22
+ const source = await readSource(join(__dirname, 'components/footer-hints.tsx'));
23
+
24
+ expect(source).not.toContain(": '0%'");
25
+ });
26
+ });
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import {
4
+ DEFAULT_ATTACHMENT_MODEL_CAPABILITIES,
5
+ resolveAttachmentModelCapabilities,
6
+ } from './attachment-capabilities';
7
+
8
+ describe('attachment-capabilities', () => {
9
+ it('defaults all modalities to false when model config omits them', () => {
10
+ expect(resolveAttachmentModelCapabilities(undefined)).toEqual(
11
+ DEFAULT_ATTACHMENT_MODEL_CAPABILITIES
12
+ );
13
+ });
14
+
15
+ it('maps explicit modality flags from model config', () => {
16
+ expect(
17
+ resolveAttachmentModelCapabilities({
18
+ modalities: {
19
+ image: true,
20
+ audio: false,
21
+ video: true,
22
+ },
23
+ })
24
+ ).toEqual({
25
+ image: true,
26
+ audio: false,
27
+ video: true,
28
+ });
29
+ });
30
+ });
@@ -0,0 +1,50 @@
1
+ import type { PromptFileSelection } from './types';
2
+
3
+ export type AttachmentModelCapabilities = {
4
+ image: boolean;
5
+ audio: boolean;
6
+ video: boolean;
7
+ };
8
+
9
+ export const DEFAULT_ATTACHMENT_MODEL_CAPABILITIES: AttachmentModelCapabilities = {
10
+ image: false,
11
+ audio: false,
12
+ video: false,
13
+ };
14
+
15
+ const normalizeFlag = (value: boolean | undefined): boolean => value === true;
16
+
17
+ export const resolveAttachmentModelCapabilities = (
18
+ modelConfig:
19
+ | {
20
+ modalities?: {
21
+ image?: boolean;
22
+ audio?: boolean;
23
+ video?: boolean;
24
+ };
25
+ }
26
+ | null
27
+ | undefined
28
+ ): AttachmentModelCapabilities => {
29
+ return {
30
+ image: normalizeFlag(modelConfig?.modalities?.image),
31
+ audio: normalizeFlag(modelConfig?.modalities?.audio),
32
+ video: normalizeFlag(modelConfig?.modalities?.video),
33
+ };
34
+ };
35
+
36
+ export const isImageSelection = (file: PromptFileSelection): boolean => {
37
+ return /\.(gif|jpe?g|png|webp)$/i.test(file.relativePath);
38
+ };
39
+
40
+ export const isAudioSelection = (file: PromptFileSelection): boolean => {
41
+ return /\.(mp3|wav|m4a|aac|ogg|flac)$/i.test(file.relativePath);
42
+ };
43
+
44
+ export const isVideoSelection = (file: PromptFileSelection): boolean => {
45
+ return /\.(mp4|mov|webm|m4v|avi|mkv)$/i.test(file.relativePath);
46
+ };
47
+
48
+ export const isMediaSelection = (file: PromptFileSelection): boolean => {
49
+ return isImageSelection(file) || isAudioSelection(file) || isVideoSelection(file);
50
+ };