@blackbox_ai/blackbox-cli 0.8.6 → 1.0.2

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 (233) hide show
  1. package/README.md +20 -0
  2. package/dist/assets/notification.mp3 +0 -0
  3. package/dist/package.json +2 -2
  4. package/dist/src/commands/configure/ConfigureUI.d.ts +1 -1
  5. package/dist/src/commands/configure/ConfigureUI.js +91 -22
  6. package/dist/src/commands/configure/ConfigureUI.js.map +1 -1
  7. package/dist/src/commands/configure.js +57 -16
  8. package/dist/src/commands/configure.js.map +1 -1
  9. package/dist/src/commands/shortcut.d.ts +10 -0
  10. package/dist/src/commands/shortcut.js +72 -0
  11. package/dist/src/commands/shortcut.js.map +1 -0
  12. package/dist/src/commands/voice.js +39 -34
  13. package/dist/src/commands/voice.js.map +1 -1
  14. package/dist/src/config/config.js +8 -5
  15. package/dist/src/config/config.js.map +1 -1
  16. package/dist/src/config/modelFetcher.d.ts +4 -0
  17. package/dist/src/config/modelFetcher.js +68 -22
  18. package/dist/src/config/modelFetcher.js.map +1 -1
  19. package/dist/src/config/settings.d.ts +2 -0
  20. package/dist/src/config/settings.js +48 -3
  21. package/dist/src/config/settings.js.map +1 -1
  22. package/dist/src/config/settingsSchema.d.ts +64 -6
  23. package/dist/src/config/settingsSchema.js +62 -6
  24. package/dist/src/config/settingsSchema.js.map +1 -1
  25. package/dist/src/gemini.js +31 -20
  26. package/dist/src/gemini.js.map +1 -1
  27. package/dist/src/generated/git-commit.d.ts +2 -2
  28. package/dist/src/generated/git-commit.js +2 -2
  29. package/dist/src/services/BuiltinCommandLoader.js +4 -2
  30. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  31. package/dist/src/services/agentExecutor.d.ts +57 -0
  32. package/dist/src/services/agentExecutor.js +149 -0
  33. package/dist/src/services/agentExecutor.js.map +1 -0
  34. package/dist/src/services/voiceRecordingService.d.ts +13 -1
  35. package/dist/src/services/voiceRecordingService.js +38 -5
  36. package/dist/src/services/voiceRecordingService.js.map +1 -1
  37. package/dist/src/ui/App.d.ts +1 -1
  38. package/dist/src/ui/App.js +292 -26
  39. package/dist/src/ui/App.js.map +1 -1
  40. package/dist/src/ui/colors.js +3 -0
  41. package/dist/src/ui/colors.js.map +1 -1
  42. package/dist/src/ui/commands/agentCommand.d.ts +7 -0
  43. package/dist/src/ui/commands/agentCommand.js +181 -0
  44. package/dist/src/ui/commands/agentCommand.js.map +1 -0
  45. package/dist/src/ui/commands/modelCommand.js +15 -2
  46. package/dist/src/ui/commands/modelCommand.js.map +1 -1
  47. package/dist/src/ui/commands/quitCommand.js +5 -0
  48. package/dist/src/ui/commands/quitCommand.js.map +1 -1
  49. package/dist/src/ui/commands/subAgentsCommand.d.ts +7 -0
  50. package/dist/src/ui/commands/subAgentsCommand.js +32 -0
  51. package/dist/src/ui/commands/subAgentsCommand.js.map +1 -0
  52. package/dist/src/ui/commands/types.d.ts +24 -3
  53. package/dist/src/ui/commands/types.js.map +1 -1
  54. package/dist/src/ui/commands/voiceCommand.js +70 -34
  55. package/dist/src/ui/commands/voiceCommand.js.map +1 -1
  56. package/dist/src/ui/commands/voiceCommand.test.d.ts +6 -0
  57. package/dist/src/ui/commands/voiceCommand.test.js +131 -0
  58. package/dist/src/ui/commands/voiceCommand.test.js.map +1 -0
  59. package/dist/src/ui/components/AgentModelSelector.d.ts +17 -0
  60. package/dist/src/ui/components/AgentModelSelector.js +41 -0
  61. package/dist/src/ui/components/AgentModelSelector.js.map +1 -0
  62. package/dist/src/ui/components/AgentSetDialog.d.ts +15 -0
  63. package/dist/src/ui/components/AgentSetDialog.js +52 -0
  64. package/dist/src/ui/components/AgentSetDialog.js.map +1 -0
  65. package/dist/src/ui/components/ApiKeyInputDialog.d.ts +17 -0
  66. package/dist/src/ui/components/ApiKeyInputDialog.js +44 -0
  67. package/dist/src/ui/components/ApiKeyInputDialog.js.map +1 -0
  68. package/dist/src/ui/components/AsciiArt.d.ts +3 -3
  69. package/dist/src/ui/components/AsciiArt.js +18 -18
  70. package/dist/src/ui/components/AsciiArt.js.map +1 -1
  71. package/dist/src/ui/components/AuthDialog.js +50 -7
  72. package/dist/src/ui/components/AuthDialog.js.map +1 -1
  73. package/dist/src/ui/components/Footer.d.ts +8 -0
  74. package/dist/src/ui/components/Footer.js +17 -4
  75. package/dist/src/ui/components/Footer.js.map +1 -1
  76. package/dist/src/ui/components/GenericProviderKeyPrompt.js +1 -1
  77. package/dist/src/ui/components/GenericProviderKeyPrompt.js.map +1 -1
  78. package/dist/src/ui/components/Header.js +6 -10
  79. package/dist/src/ui/components/Header.js.map +1 -1
  80. package/dist/src/ui/components/HistoryBrowserDialog.js +2 -1
  81. package/dist/src/ui/components/HistoryBrowserDialog.js.map +1 -1
  82. package/dist/src/ui/components/InputPrompt.js +3 -1
  83. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  84. package/dist/src/ui/components/MemoryUsageDisplay.js +41 -8
  85. package/dist/src/ui/components/MemoryUsageDisplay.js.map +1 -1
  86. package/dist/src/ui/components/SettingsDialog.js +4 -3
  87. package/dist/src/ui/components/SettingsDialog.js.map +1 -1
  88. package/dist/src/ui/components/StatsDisplay.js +1 -1
  89. package/dist/src/ui/components/StatsDisplay.js.map +1 -1
  90. package/dist/src/ui/components/SuggestionsDisplay.js +3 -2
  91. package/dist/src/ui/components/SuggestionsDisplay.js.map +1 -1
  92. package/dist/src/ui/components/Tips.js +1 -1
  93. package/dist/src/ui/components/Tips.js.map +1 -1
  94. package/dist/src/ui/components/agents/SingleAgentDialog.d.ts +23 -0
  95. package/dist/src/ui/components/agents/SingleAgentDialog.js +222 -0
  96. package/dist/src/ui/components/agents/SingleAgentDialog.js.map +1 -0
  97. package/dist/src/ui/components/messages/DiffRenderer.js +1 -9
  98. package/dist/src/ui/components/messages/DiffRenderer.js.map +1 -1
  99. package/dist/src/ui/components/messages/ErrorMessage.js +2 -1
  100. package/dist/src/ui/components/messages/ErrorMessage.js.map +1 -1
  101. package/dist/src/ui/components/messages/InfoMessage.js +2 -1
  102. package/dist/src/ui/components/messages/InfoMessage.js.map +1 -1
  103. package/dist/src/ui/components/messages/ToolGroupMessage.js +4 -1
  104. package/dist/src/ui/components/messages/ToolGroupMessage.js.map +1 -1
  105. package/dist/src/ui/components/messages/ToolMessage.js +4 -4
  106. package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
  107. package/dist/src/ui/components/messages/UserMessage.js +1 -2
  108. package/dist/src/ui/components/messages/UserMessage.js.map +1 -1
  109. package/dist/src/ui/components/multiagent/MultiAgentConfigDialog.js +1 -19
  110. package/dist/src/ui/components/multiagent/MultiAgentConfigDialog.js.map +1 -1
  111. package/dist/src/ui/components/shared/RadioButtonSelect.js +14 -4
  112. package/dist/src/ui/components/shared/RadioButtonSelect.js.map +1 -1
  113. package/dist/src/ui/contexts/SessionContext.d.ts +18 -0
  114. package/dist/src/ui/contexts/SessionContext.js +25 -1
  115. package/dist/src/ui/contexts/SessionContext.js.map +1 -1
  116. package/dist/src/ui/hooks/shellCommandProcessor.d.ts +1 -1
  117. package/dist/src/ui/hooks/shellCommandProcessor.js +13 -2
  118. package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
  119. package/dist/src/ui/hooks/slashCommandProcessor.d.ts +2 -2
  120. package/dist/src/ui/hooks/slashCommandProcessor.js +65 -29
  121. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  122. package/dist/src/ui/hooks/useAdaptiveStream.d.ts +2 -1
  123. package/dist/src/ui/hooks/useAdaptiveStream.js +2 -2
  124. package/dist/src/ui/hooks/useAdaptiveStream.js.map +1 -1
  125. package/dist/src/ui/hooks/useAgentCommandProcessor.d.ts +26 -0
  126. package/dist/src/ui/hooks/useAgentCommandProcessor.js +967 -0
  127. package/dist/src/ui/hooks/useAgentCommandProcessor.js.map +1 -0
  128. package/dist/src/ui/hooks/useAuthCommand.d.ts +2 -1
  129. package/dist/src/ui/hooks/useAuthCommand.js +22 -2
  130. package/dist/src/ui/hooks/useAuthCommand.js.map +1 -1
  131. package/dist/src/ui/hooks/useDialogClose.d.ts +2 -0
  132. package/dist/src/ui/hooks/useDialogClose.js +5 -0
  133. package/dist/src/ui/hooks/useDialogClose.js.map +1 -1
  134. package/dist/src/ui/hooks/useEncryptedStream.d.ts +2 -1
  135. package/dist/src/ui/hooks/useEncryptedStream.js +31 -3
  136. package/dist/src/ui/hooks/useEncryptedStream.js.map +1 -1
  137. package/dist/src/ui/hooks/useGeminiStream.d.ts +2 -1
  138. package/dist/src/ui/hooks/useGeminiStream.js +292 -22
  139. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  140. package/dist/src/ui/hooks/useInputBoxColor.d.ts +13 -0
  141. package/dist/src/ui/hooks/useInputBoxColor.js +62 -0
  142. package/dist/src/ui/hooks/useInputBoxColor.js.map +1 -0
  143. package/dist/src/ui/hooks/useMessageQueue.d.ts +7 -1
  144. package/dist/src/ui/hooks/useMessageQueue.js +9 -3
  145. package/dist/src/ui/hooks/useMessageQueue.js.map +1 -1
  146. package/dist/src/ui/hooks/useSingleAgentCommand.d.ts +10 -0
  147. package/dist/src/ui/hooks/useSingleAgentCommand.js +21 -0
  148. package/dist/src/ui/hooks/useSingleAgentCommand.js.map +1 -0
  149. package/dist/src/ui/themes/ansi-light.js +1 -0
  150. package/dist/src/ui/themes/ansi-light.js.map +1 -1
  151. package/dist/src/ui/themes/ansi.js +1 -0
  152. package/dist/src/ui/themes/ansi.js.map +1 -1
  153. package/dist/src/ui/themes/atom-one-dark.js +14 -13
  154. package/dist/src/ui/themes/atom-one-dark.js.map +1 -1
  155. package/dist/src/ui/themes/ayu-light.js +14 -13
  156. package/dist/src/ui/themes/ayu-light.js.map +1 -1
  157. package/dist/src/ui/themes/ayu.js +14 -13
  158. package/dist/src/ui/themes/ayu.js.map +1 -1
  159. package/dist/src/ui/themes/blackbox-dark.js +36 -31
  160. package/dist/src/ui/themes/blackbox-dark.js.map +1 -1
  161. package/dist/src/ui/themes/blackbox-light.js +41 -39
  162. package/dist/src/ui/themes/blackbox-light.js.map +1 -1
  163. package/dist/src/ui/themes/default-light.js +20 -20
  164. package/dist/src/ui/themes/default-light.js.map +1 -1
  165. package/dist/src/ui/themes/default.js +27 -27
  166. package/dist/src/ui/themes/default.js.map +1 -1
  167. package/dist/src/ui/themes/dracula.js +14 -13
  168. package/dist/src/ui/themes/dracula.js.map +1 -1
  169. package/dist/src/ui/themes/github-dark.js +14 -13
  170. package/dist/src/ui/themes/github-dark.js.map +1 -1
  171. package/dist/src/ui/themes/github-light.js +14 -13
  172. package/dist/src/ui/themes/github-light.js.map +1 -1
  173. package/dist/src/ui/themes/googlecode.js +1 -0
  174. package/dist/src/ui/themes/googlecode.js.map +1 -1
  175. package/dist/src/ui/themes/no-color.js +2 -0
  176. package/dist/src/ui/themes/no-color.js.map +1 -1
  177. package/dist/src/ui/themes/semantic-tokens.d.ts +1 -0
  178. package/dist/src/ui/themes/semantic-tokens.js +3 -0
  179. package/dist/src/ui/themes/semantic-tokens.js.map +1 -1
  180. package/dist/src/ui/themes/shades-of-purple.js +1 -0
  181. package/dist/src/ui/themes/shades-of-purple.js.map +1 -1
  182. package/dist/src/ui/themes/theme.d.ts +2 -0
  183. package/dist/src/ui/themes/theme.js +35 -28
  184. package/dist/src/ui/themes/theme.js.map +1 -1
  185. package/dist/src/ui/themes/xcode.js +1 -0
  186. package/dist/src/ui/themes/xcode.js.map +1 -1
  187. package/dist/src/ui/types.d.ts +5 -0
  188. package/dist/src/ui/utils/agentConversationUtils.d.ts +28 -0
  189. package/dist/src/ui/utils/agentConversationUtils.js +71 -0
  190. package/dist/src/ui/utils/agentConversationUtils.js.map +1 -0
  191. package/dist/src/ui/utils/colorBlend.d.ts +33 -0
  192. package/dist/src/ui/utils/colorBlend.js +61 -0
  193. package/dist/src/ui/utils/colorBlend.js.map +1 -0
  194. package/dist/src/ui/utils/commandUtils.d.ts +2 -1
  195. package/dist/src/ui/utils/commandUtils.js +14 -1
  196. package/dist/src/ui/utils/commandUtils.js.map +1 -1
  197. package/dist/src/ui/utils/commandUtils.test.js +25 -0
  198. package/dist/src/ui/utils/commandUtils.test.js.map +1 -1
  199. package/dist/src/ui/utils/terminalBackgroundDetector.d.ts +35 -0
  200. package/dist/src/ui/utils/terminalBackgroundDetector.js +200 -0
  201. package/dist/src/ui/utils/terminalBackgroundDetector.js.map +1 -0
  202. package/dist/src/utils/agentCommandBuilder.d.ts +48 -0
  203. package/dist/src/utils/agentCommandBuilder.js +328 -0
  204. package/dist/src/utils/agentCommandBuilder.js.map +1 -0
  205. package/dist/src/utils/apiKeyUtils.d.ts +47 -0
  206. package/dist/src/utils/apiKeyUtils.js +177 -0
  207. package/dist/src/utils/apiKeyUtils.js.map +1 -0
  208. package/dist/src/utils/backgroundUpdateCheck.js +104 -7
  209. package/dist/src/utils/backgroundUpdateCheck.js.map +1 -1
  210. package/dist/src/utils/memoryManager.d.ts +85 -0
  211. package/dist/src/utils/memoryManager.js +258 -0
  212. package/dist/src/utils/memoryManager.js.map +1 -0
  213. package/dist/src/utils/memoryMonitor.d.ts +89 -0
  214. package/dist/src/utils/memoryMonitor.js +238 -0
  215. package/dist/src/utils/memoryMonitor.js.map +1 -0
  216. package/dist/src/utils/memoryWrapper.d.ts +18 -0
  217. package/dist/src/utils/memoryWrapper.js +62 -0
  218. package/dist/src/utils/memoryWrapper.js.map +1 -0
  219. package/dist/src/utils/preLaunchUpdateCheck.js +17 -7
  220. package/dist/src/utils/preLaunchUpdateCheck.js.map +1 -1
  221. package/dist/src/utils/shellShortcut.d.ts +45 -0
  222. package/dist/src/utils/shellShortcut.js +221 -0
  223. package/dist/src/utils/shellShortcut.js.map +1 -0
  224. package/dist/src/utils/soundNotification.d.ts +15 -0
  225. package/dist/src/utils/soundNotification.js +138 -0
  226. package/dist/src/utils/soundNotification.js.map +1 -0
  227. package/dist/src/utils/userStartupWarnings.js +0 -20
  228. package/dist/src/utils/userStartupWarnings.js.map +1 -1
  229. package/dist/src/utils/versionStorage.d.ts +20 -0
  230. package/dist/src/utils/versionStorage.js +62 -0
  231. package/dist/src/utils/versionStorage.js.map +1 -1
  232. package/dist/tsconfig.tsbuildinfo +1 -1
  233. package/package.json +2 -2
@@ -0,0 +1,967 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Blackbox
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { useCallback, useRef } from 'react';
7
+ import { ToolCallStatus } from '../types.js';
8
+ import { AgentExecutor } from '../../services/agentExecutor.js';
9
+ /**
10
+ * Enhanced parser to extract tool information from agent output
11
+ */
12
+ function extractToolCallFromAgentOutput(toolContent) {
13
+ const lines = toolContent.split('\n');
14
+ let toolName = '';
15
+ let args = {};
16
+ let inPayload = false;
17
+ let payloadContent = '';
18
+ for (const line of lines) {
19
+ const cleanLine = line.replace(/[│\s]/g, '');
20
+ // Extract tool name
21
+ if (cleanLine.startsWith('Tool')) {
22
+ toolName = cleanLine.substring(4).trim();
23
+ continue;
24
+ }
25
+ // Start of invocation payload
26
+ if (cleanLine.includes('Invocationpayload:') || cleanLine.includes('payload:')) {
27
+ inPayload = true;
28
+ continue;
29
+ }
30
+ // End of payload section
31
+ if (cleanLine.includes('Result:') || cleanLine.includes('Output:')) {
32
+ inPayload = false;
33
+ break;
34
+ }
35
+ // Collect payload content
36
+ if (inPayload) {
37
+ payloadContent += line + '\n';
38
+ }
39
+ }
40
+ // Parse JSON payload if present
41
+ try {
42
+ if (payloadContent.trim()) {
43
+ // Clean up the payload content and try to parse as JSON
44
+ const cleanPayload = payloadContent.trim().replace(/^[{]/, '{').replace(/[}]$/, '}');
45
+ args = JSON.parse(cleanPayload);
46
+ }
47
+ }
48
+ catch {
49
+ // If JSON parsing fails, return null - this isn't a valid tool call
50
+ return null;
51
+ }
52
+ if (!toolName || Object.keys(args).length === 0) {
53
+ return null;
54
+ }
55
+ return {
56
+ callId: `agent-tool-${Date.now()}-${Math.random().toString(16).slice(2)}`,
57
+ name: toolName,
58
+ args,
59
+ isClientInitiated: false, // These come from the agent, not user
60
+ prompt_id: `agent-${Date.now()}`,
61
+ };
62
+ }
63
+ /**
64
+ * Parse Gemini agent streaming JSON output to extract text content and tool information
65
+ */
66
+ function parseGeminiStreamingOutput(jsonLine) {
67
+ try {
68
+ const parsed = JSON.parse(jsonLine);
69
+ // Handle streaming text deltas
70
+ if (parsed.type === 'message' && parsed.role === 'assistant' && parsed.content && parsed.delta === true) {
71
+ return { type: 'delta', text: parsed.content };
72
+ }
73
+ // Handle complete assistant messages
74
+ if (parsed.type === 'message' && parsed.role === 'assistant' && parsed.content && !parsed.delta) {
75
+ return { type: 'final', text: parsed.content };
76
+ }
77
+ // Handle tool use events
78
+ if (parsed.type === 'tool_use') {
79
+ const toolCall = {
80
+ callId: parsed.tool_use_id || `gemini-tool-${Date.now()}-${Math.random().toString(16).slice(2)}`,
81
+ name: parsed.tool_name,
82
+ args: parsed.parameters || {},
83
+ isClientInitiated: false, // These come from Gemini, not user
84
+ prompt_id: `gemini-${Date.now()}`,
85
+ };
86
+ return { type: 'tool_use', toolCall };
87
+ }
88
+ // Handle tool results
89
+ if (parsed.type === 'tool_result') {
90
+ return {
91
+ type: 'tool_result',
92
+ toolResult: {
93
+ toolId: parsed.tool_use_id,
94
+ result: parsed.output || parsed.result || ''
95
+ }
96
+ };
97
+ }
98
+ // Handle final result
99
+ if (parsed.type === 'result' && parsed.status === 'success') {
100
+ return { type: 'final', text: parsed.result || '' };
101
+ }
102
+ // Skip init, system messages and other metadata
103
+ if (parsed.type === 'init' || parsed.type === 'system') {
104
+ return { type: 'skip' };
105
+ }
106
+ // Skip other metadata but log for debugging
107
+ return { type: 'skip' };
108
+ }
109
+ catch (error) {
110
+ console.log('[Gemini Parser] JSON parse error:', error);
111
+ return { type: 'skip' };
112
+ }
113
+ }
114
+ /**
115
+ * Parse Codex agent streaming JSON output to extract text content and tool information
116
+ */
117
+ function parseCodexStreamingOutput(jsonLine) {
118
+ try {
119
+ const parsed = JSON.parse(jsonLine);
120
+ // Handle agent messages (text content)
121
+ if (parsed.type === 'item.completed' &&
122
+ parsed.item?.type === 'agent_message' &&
123
+ parsed.item?.text) {
124
+ return { type: 'delta', text: parsed.item.text + '\n\n' };
125
+ }
126
+ // Handle command execution start (tool use)
127
+ if (parsed.type === 'item.started' &&
128
+ parsed.item?.type === 'command_execution' &&
129
+ parsed.item?.command) {
130
+ // Create a tool call for command execution
131
+ const toolCall = {
132
+ callId: parsed.item.id || `codex-cmd-${Date.now()}-${Math.random().toString(16).slice(2)}`,
133
+ name: 'shell',
134
+ args: { command: parsed.item.command },
135
+ isClientInitiated: false,
136
+ prompt_id: `codex-${Date.now()}`,
137
+ };
138
+ return { type: 'tool_use', toolCall };
139
+ }
140
+ // Handle command execution completion (tool result)
141
+ if (parsed.type === 'item.completed' &&
142
+ parsed.item?.type === 'command_execution') {
143
+ const exitCode = parsed.item.exit_code;
144
+ return {
145
+ type: 'tool_result',
146
+ toolResult: {
147
+ toolId: parsed.item.id,
148
+ result: exitCode === 0 ? 'Command completed successfully' : `Command failed with exit code: ${exitCode}`
149
+ }
150
+ };
151
+ }
152
+ // Handle thread start
153
+ if (parsed.type === 'thread.started') {
154
+ return { type: 'skip' }; // Skip thread metadata
155
+ }
156
+ // Skip other metadata
157
+ return { type: 'skip' };
158
+ }
159
+ catch (error) {
160
+ console.log('[Codex Parser] JSON parse error:', error);
161
+ return { type: 'skip' };
162
+ }
163
+ }
164
+ /**
165
+ * Parse Claude Code agent streaming JSON output to extract text content and tool information
166
+ */
167
+ function parseClaudeStreamingOutput(jsonLine) {
168
+ try {
169
+ const parsed = JSON.parse(jsonLine);
170
+ // Handle streaming text deltas
171
+ if (parsed.type === 'stream_event' &&
172
+ parsed.event?.type === 'content_block_delta' &&
173
+ parsed.event?.delta?.type === 'text_delta') {
174
+ return { type: 'delta', text: parsed.event.delta.text };
175
+ }
176
+ // Handle tool use in assistant messages
177
+ if (parsed.type === 'assistant' && parsed.message?.content) {
178
+ const content = parsed.message.content;
179
+ if (Array.isArray(content)) {
180
+ // Check for tool use
181
+ const toolUse = content.find(item => item.type === 'tool_use');
182
+ if (toolUse) {
183
+ // Create a proper ToolCallRequestInfo for the scheduler
184
+ const toolCall = {
185
+ callId: toolUse.id || `claude-tool-${Date.now()}-${Math.random().toString(16).slice(2)}`,
186
+ name: toolUse.name,
187
+ args: toolUse.input || {},
188
+ isClientInitiated: false, // These come from Claude, not user
189
+ prompt_id: `claude-${Date.now()}`,
190
+ };
191
+ return { type: 'tool_use', toolCall };
192
+ }
193
+ // Extract text content
194
+ const text = content
195
+ .filter(item => item.type === 'text')
196
+ .map(item => item.text)
197
+ .join('\n');
198
+ if (text) {
199
+ return { type: 'final', text };
200
+ }
201
+ }
202
+ return { type: 'final', text: content };
203
+ }
204
+ // Handle tool results in user messages
205
+ if (parsed.type === 'user' && parsed.message?.content) {
206
+ const content = parsed.message.content;
207
+ if (Array.isArray(content)) {
208
+ const toolResult = content.find(item => item.type === 'tool_result');
209
+ if (toolResult) {
210
+ // Return tool result information for the scheduler to handle
211
+ return {
212
+ type: 'tool_result',
213
+ toolResult: {
214
+ toolId: toolResult.tool_use_id,
215
+ result: typeof toolResult.content === 'string' ? toolResult.content : JSON.stringify(toolResult.content)
216
+ }
217
+ };
218
+ }
219
+ }
220
+ }
221
+ // Handle final result (this is the cleanest output)
222
+ if (parsed.type === 'result' && parsed.result) {
223
+ return { type: 'final', text: parsed.result };
224
+ }
225
+ // Handle streaming result chunks - when Claude sends partial result data
226
+ if (parsed.type === 'result' && parsed.partial_result) {
227
+ return { type: 'delta', text: parsed.partial_result };
228
+ }
229
+ // Skip system messages and other metadata
230
+ return { type: 'skip' };
231
+ }
232
+ catch (error) {
233
+ console.log('[Claude Parser] JSON parse error:', error);
234
+ return { type: 'skip' };
235
+ }
236
+ }
237
+ /**
238
+ * Parse agent output to extract tool calls and clean message content
239
+ */
240
+ function parseAgentOutputForToolCalls(output, lastProcessedLength, scheduleToolCalls, abortSignal, agentType) {
241
+ // Only process new content since last parse
242
+ const newContent = output.substring(lastProcessedLength);
243
+ if (!newContent) {
244
+ return { newProcessedLength: lastProcessedLength, messageContent: output };
245
+ }
246
+ let messageContent = '';
247
+ let currentToolContent = '';
248
+ let inToolBox = false;
249
+ const toolCallsToSchedule = [];
250
+ const lines = output.split('\n');
251
+ // Handle Claude Code agent's JSON streaming output format
252
+ if (agentType === 'claude') {
253
+ // For the fallback parsing in this context, we just use the raw output
254
+ // The actual streaming parsing happens in the main execution flow
255
+ messageContent = output;
256
+ // Fallback: if no content extracted, try the old method
257
+ if (!messageContent) {
258
+ const cleanMessages = [];
259
+ for (const line of lines) {
260
+ const trimmedLine = line.trim();
261
+ // Skip tool call indicators, empty lines, and JSON lines
262
+ if (trimmedLine.startsWith('✦ ') || !trimmedLine ||
263
+ (trimmedLine.startsWith('{') && trimmedLine.endsWith('}'))) {
264
+ continue;
265
+ }
266
+ // Add regular text content
267
+ cleanMessages.push(trimmedLine);
268
+ }
269
+ messageContent = cleanMessages.join('\n\n').trim();
270
+ }
271
+ }
272
+ else {
273
+ // Handle other agents with tool box format
274
+ for (let i = 0; i < lines.length; i++) {
275
+ const line = lines[i];
276
+ // Detect tool box start
277
+ if (line.includes('┌─')) {
278
+ inToolBox = true;
279
+ currentToolContent = '';
280
+ continue;
281
+ }
282
+ // Detect tool box end
283
+ if (line.includes('└─') && inToolBox) {
284
+ inToolBox = false;
285
+ // Try to extract a tool call from the content
286
+ const toolCall = extractToolCallFromAgentOutput(currentToolContent);
287
+ if (toolCall) {
288
+ toolCallsToSchedule.push(toolCall);
289
+ }
290
+ else {
291
+ // If it's not a valid tool call, add it to message content
292
+ messageContent += `\n┌─\n${currentToolContent}\n└─\n`;
293
+ }
294
+ continue;
295
+ }
296
+ // Collect tool box content
297
+ if (inToolBox) {
298
+ currentToolContent += line + '\n';
299
+ continue;
300
+ }
301
+ // Regular message content (skip tool call indicators)
302
+ if (!line.startsWith('✦ ')) {
303
+ messageContent += line + '\n';
304
+ }
305
+ }
306
+ messageContent = messageContent.trim();
307
+ }
308
+ // Schedule any tool calls we found
309
+ if (toolCallsToSchedule.length > 0) {
310
+ scheduleToolCalls(toolCallsToSchedule, abortSignal);
311
+ }
312
+ return {
313
+ newProcessedLength: output.length,
314
+ messageContent
315
+ };
316
+ }
317
+ /**
318
+ * Format a tool call for inclusion in conversation history.
319
+ * This ensures the agent has context about what tools were used.
320
+ */
321
+ function formatToolCallForHistory(toolCall) {
322
+ const toolName = toolCall.name;
323
+ const args = toolCall.args || {};
324
+ // Extract the most relevant argument for the summary
325
+ let summary = '';
326
+ const toolNameLower = toolName.toLowerCase();
327
+ if (toolNameLower.includes('write') || toolNameLower.includes('edit')) {
328
+ const filePath = (args['file_path'] || args['path'] || args['target_file']);
329
+ summary = filePath ? `file: ${filePath}` : 'file operation';
330
+ }
331
+ else if (toolNameLower.includes('read')) {
332
+ const filePath = (args['file_path'] || args['path'] || args['target_file']);
333
+ summary = filePath ? `file: ${filePath}` : 'file read';
334
+ }
335
+ else if (toolNameLower.includes('shell') || toolNameLower.includes('bash') || toolNameLower.includes('execute')) {
336
+ const command = (args['command'] || args['cmd']);
337
+ summary = command ? `command: ${command.substring(0, 100)}${command.length > 100 ? '...' : ''}` : 'command execution';
338
+ }
339
+ else if (toolNameLower.includes('search') || toolNameLower.includes('grep')) {
340
+ const query = (args['query'] || args['pattern'] || args['search_query']);
341
+ summary = query ? `query: ${query}` : 'search';
342
+ }
343
+ else if (toolNameLower.includes('list') || toolNameLower.includes('directory')) {
344
+ const path = (args['path'] || args['directory']);
345
+ summary = path ? `path: ${path}` : 'directory listing';
346
+ }
347
+ else if (toolNameLower.includes('todo')) {
348
+ const todos = args['todos'];
349
+ summary = todos ? `${todos.length} todo(s)` : 'todo list';
350
+ }
351
+ else {
352
+ // Generic fallback - show first argument
353
+ const firstKey = Object.keys(args)[0];
354
+ if (firstKey) {
355
+ const firstValue = args[firstKey];
356
+ if (typeof firstValue === 'string') {
357
+ summary = `${firstKey}: ${firstValue.substring(0, 50)}${firstValue.length > 50 ? '...' : ''}`;
358
+ }
359
+ else {
360
+ summary = `${firstKey}: ${JSON.stringify(firstValue).substring(0, 50)}`;
361
+ }
362
+ }
363
+ }
364
+ return `[Tool Used: ${toolName}${summary ? ` - ${summary}` : ''}]`;
365
+ }
366
+ /**
367
+ * Format a tool result for inclusion in conversation history.
368
+ * Extracts and truncates the actual content from tool results.
369
+ */
370
+ function formatToolResultForHistory(toolId, result) {
371
+ try {
372
+ // Try to parse result as JSON to extract meaningful content
373
+ const parsed = JSON.parse(result);
374
+ // Handle different tool result formats
375
+ if (parsed.content !== undefined) {
376
+ // ReadFile, WriteFile, Edit tools typically have 'content' field
377
+ const content = typeof parsed.content === 'string' ? parsed.content : JSON.stringify(parsed.content);
378
+ const truncated = content.length > 300 ? content.substring(0, 300) + '...(truncated)' : content;
379
+ return `[Tool Result: ${truncated}]`;
380
+ }
381
+ else if (parsed.data !== undefined) {
382
+ // ReadDataFile and similar tools have 'data' field
383
+ const data = typeof parsed.data === 'string' ? parsed.data : JSON.stringify(parsed.data);
384
+ const truncated = data.length > 300 ? data.substring(0, 300) + '...(truncated)' : data;
385
+ return `[Tool Result: ${truncated}]`;
386
+ }
387
+ else if (parsed.results !== undefined) {
388
+ // WebSearch typically has 'results' array
389
+ const results = Array.isArray(parsed.results) ? parsed.results : [parsed.results];
390
+ const summary = results.slice(0, 3).map((r) => {
391
+ if (typeof r === 'string')
392
+ return r;
393
+ if (r && typeof r === 'object') {
394
+ const obj = r;
395
+ if (obj['title'] && obj['snippet'])
396
+ return `${obj['title']}: ${obj['snippet']}`;
397
+ if (obj['title'])
398
+ return String(obj['title']);
399
+ }
400
+ return JSON.stringify(r);
401
+ }).join('; ');
402
+ const truncated = summary.length > 300 ? summary.substring(0, 300) + '...(truncated)' : summary;
403
+ return `[Tool Result: ${truncated}]`;
404
+ }
405
+ else if (parsed.html !== undefined || parsed.text !== undefined) {
406
+ // WebFetch typically has 'html' or 'text' field
407
+ const content = parsed.text || parsed.html || '';
408
+ const truncated = content.length > 300 ? content.substring(0, 300) + '...(truncated)' : content;
409
+ return `[Tool Result: ${truncated}]`;
410
+ }
411
+ else if (parsed.output !== undefined) {
412
+ // Shell command output
413
+ const output = typeof parsed.output === 'string' ? parsed.output : JSON.stringify(parsed.output);
414
+ const truncated = output.length > 300 ? output.substring(0, 300) + '...(truncated)' : output;
415
+ return `[Tool Result: ${truncated}]`;
416
+ }
417
+ else if (parsed.success !== undefined) {
418
+ // Success/failure result
419
+ const message = parsed.message || parsed.error || (parsed.success ? 'Success' : 'Failed');
420
+ return `[Tool Result: ${message}]`;
421
+ }
422
+ else {
423
+ // Generic JSON result
424
+ const jsonStr = JSON.stringify(parsed);
425
+ const truncated = jsonStr.length > 300 ? jsonStr.substring(0, 300) + '...(truncated)' : jsonStr;
426
+ return `[Tool Result: ${truncated}]`;
427
+ }
428
+ }
429
+ catch {
430
+ // If not JSON, treat as plain text
431
+ const truncated = result.length > 300 ? result.substring(0, 300) + '...(truncated)' : result;
432
+ return `[Tool Result: ${truncated}]`;
433
+ }
434
+ }
435
+ export const useAgentCommandProcessor = (addItemToHistory, setPendingHistoryItem, onExec, onDebugMessage, config, scheduleToolCalls, clearActiveAgentSession, settings) => {
436
+ // Track the current pending item state with a ref for reading
437
+ const currentPendingItemRef = useRef(null);
438
+ // Wrapper to sync the ref when setPendingHistoryItem is called
439
+ const syncedSetPendingHistoryItem = useCallback((newItem) => {
440
+ currentPendingItemRef.current = newItem;
441
+ setPendingHistoryItem(newItem);
442
+ }, [setPendingHistoryItem]);
443
+ // Claude/Gemini streaming handler - exactly like Gemini's handleContentEvent
444
+ const handleStreamingContentEvent = useCallback((textDelta, currentMessageBuffer, userMessageTimestamp) => {
445
+ let newMessageBuffer = currentMessageBuffer + textDelta;
446
+ if (currentPendingItemRef.current?.type !== 'gemini' &&
447
+ currentPendingItemRef.current?.type !== 'gemini_content') {
448
+ if (currentPendingItemRef.current) {
449
+ addItemToHistory(currentPendingItemRef.current, userMessageTimestamp);
450
+ }
451
+ const newItem = { type: 'gemini', text: '' };
452
+ syncedSetPendingHistoryItem(newItem);
453
+ newMessageBuffer = textDelta;
454
+ }
455
+ // Update the existing message with accumulated content (same as Gemini)
456
+ const updatedItem = {
457
+ type: (currentPendingItemRef.current?.type || 'gemini'),
458
+ text: newMessageBuffer,
459
+ };
460
+ syncedSetPendingHistoryItem(updatedItem);
461
+ return newMessageBuffer;
462
+ }, [addItemToHistory, syncedSetPendingHistoryItem]);
463
+ const executeAgent = useCallback((agentType, model, task, abortSignal, options) => {
464
+ // Early return for blackbox agent - it should be handled directly by useGeminiStream
465
+ // to reuse all streaming, tools, and UI components
466
+ if (agentType === 'blackbox') {
467
+ return;
468
+ }
469
+ const userMessageTimestamp = Date.now();
470
+ // Note: User message is already added to history by the auto-routing logic in useGeminiStream
471
+ // We don't need to add it again here to avoid showing "/agent <task>"
472
+ const executeCommand = async (resolve) => {
473
+ let cumulativeOutput = '';
474
+ let lastProcessedLength = 0;
475
+ // Start with clean slate for real-time streaming (like Gemini does)
476
+ setPendingHistoryItem(null);
477
+ const executor = new AgentExecutor();
478
+ // Check if CLI is installed (since blackbox agent already returned early,
479
+ // we only get here for claude, codex, and gemini agents)
480
+ const isInstalled = await executor.checkAgentCLI(agentType);
481
+ if (!isInstalled) {
482
+ onDebugMessage(`${agentType} CLI not found. Installing...`);
483
+ // Show installation message
484
+ setPendingHistoryItem({
485
+ type: 'gemini_content',
486
+ text: `Installing ${agentType} CLI...\n\nThis may take a few moments...`,
487
+ });
488
+ const installSuccess = await executor.installAgentCLI(agentType, (chunk) => {
489
+ onDebugMessage(`Install output: ${chunk}`);
490
+ }, abortSignal);
491
+ if (!installSuccess) {
492
+ setPendingHistoryItem(null);
493
+ addItemToHistory({
494
+ type: 'error',
495
+ text: `Failed to install ${agentType} CLI. Please install it manually.`,
496
+ }, userMessageTimestamp);
497
+ resolve();
498
+ return;
499
+ }
500
+ // Clear installation message and start agent execution
501
+ setPendingHistoryItem({
502
+ type: 'gemini_content',
503
+ text: '',
504
+ });
505
+ }
506
+ try {
507
+ // Create message buffer for streaming (like Gemini does)
508
+ let streamingMessageBuffer = '';
509
+ // Track tool calls for conversation history (multi-turn context)
510
+ const toolCallsHistory = [];
511
+ // Execute the agent with optimized streaming output
512
+ const executionPromise = executor.executeAgent({
513
+ agentType,
514
+ model,
515
+ task,
516
+ cwd: config.getTargetDir(),
517
+ pastMessages: options?.pastMessages,
518
+ settings,
519
+ onOutput: (_, chunk) => {
520
+ // Handle Claude, Codex, and Gemini streaming differently - use direct event processing like Gemini
521
+ if (agentType === 'claude' || agentType === 'codex' || agentType === 'gemini') {
522
+ // Add chunk to cumulative output for JSON buffer management
523
+ cumulativeOutput += chunk;
524
+ // Try to parse complete JSON objects from the buffer
525
+ const jsonLines = cumulativeOutput.split('\n');
526
+ let processedUpTo = 0;
527
+ for (let i = 0; i < jsonLines.length - 1; i++) { // Skip last line as it might be incomplete
528
+ const line = jsonLines[i].trim();
529
+ if (line) {
530
+ const parsed = agentType === 'claude'
531
+ ? parseClaudeStreamingOutput(line)
532
+ : agentType === 'codex'
533
+ ? parseCodexStreamingOutput(line)
534
+ : parseGeminiStreamingOutput(line);
535
+ if (parsed.type === 'delta' && parsed.text) {
536
+ streamingMessageBuffer = handleStreamingContentEvent(parsed.text, streamingMessageBuffer, userMessageTimestamp);
537
+ }
538
+ else if (parsed.type === 'tool_use' && parsed.toolCall) {
539
+ // Track tool call for conversation history (multi-turn context)
540
+ toolCallsHistory.push(formatToolCallForHistory(parsed.toolCall));
541
+ // Create a proper tool call display object for the UI
542
+ // Format it like Gemini tools: name is the tool, description is the main argument
543
+ let toolDescription = '';
544
+ if (parsed.toolCall.args) {
545
+ // Extract the most relevant argument for the description based on tool type
546
+ const args = parsed.toolCall.args;
547
+ const toolName = parsed.toolCall.name.toLowerCase();
548
+ if (toolName.includes('websearch') || toolName.includes('web_search')) {
549
+ toolDescription = (args['query'] || args['search_query']) || 'web search';
550
+ }
551
+ else if (toolName.includes('todowrite') || toolName.includes('todo_write')) {
552
+ const todos = args['todos'];
553
+ if (todos && todos.length > 0) {
554
+ toolDescription = `${todos.length} todo${todos.length > 1 ? 's' : ''}: ${todos[0].content}${todos.length > 1 ? '...' : ''}`;
555
+ }
556
+ else {
557
+ toolDescription = 'todo list';
558
+ }
559
+ }
560
+ else if (toolName.includes('edit') || toolName.includes('write')) {
561
+ toolDescription = (args['file_path'] || args['path']) || 'file operation';
562
+ }
563
+ else if (toolName.includes('read') || toolName.includes('list')) {
564
+ toolDescription = (args['file_path'] || args['path'] || args['directory']) || 'file access';
565
+ }
566
+ else if (toolName.includes('shell') || toolName.includes('execute')) {
567
+ toolDescription = (args['command'] || args['cmd']) || 'command execution';
568
+ }
569
+ else if (args['file_path'] || args['path']) {
570
+ toolDescription = (args['file_path'] || args['path']);
571
+ }
572
+ else if (args['command']) {
573
+ toolDescription = args['command'];
574
+ }
575
+ else if (args['query']) {
576
+ toolDescription = args['query'];
577
+ }
578
+ else if (args['content']) {
579
+ toolDescription = 'content provided';
580
+ }
581
+ else {
582
+ // Fallback to first argument value
583
+ const firstValue = Object.values(args)[0];
584
+ toolDescription = typeof firstValue === 'string' ? firstValue : 'execution';
585
+ }
586
+ }
587
+ // Create appropriate result display based on tool type to match ToolMessage expectations
588
+ let resultDisplay = undefined;
589
+ const command = parsed.toolCall.args?.['command'] || '';
590
+ const toolName = parsed.toolCall.name.toLowerCase();
591
+ // Handle special Codex commands with enhanced formatting
592
+ if (command.startsWith('update_plan')) {
593
+ // Extract and format plan data
594
+ const planMatch = command.match(/update_plan\s+'(.+)'/);
595
+ if (planMatch) {
596
+ try {
597
+ const planData = JSON.parse(planMatch[1]);
598
+ if (Array.isArray(planData)) {
599
+ const todoDisplay = {
600
+ type: 'todo_list',
601
+ todos: planData.map((item, index) => ({
602
+ id: `plan-${index}`,
603
+ content: item.description || 'Unknown task',
604
+ status: item.status === 'completed' ? 'completed' :
605
+ item.status === 'in_progress' ? 'in_progress' :
606
+ 'pending'
607
+ })),
608
+ };
609
+ resultDisplay = todoDisplay;
610
+ toolDescription = `Update project plan (${planData.length} tasks)`;
611
+ }
612
+ }
613
+ catch (_e) {
614
+ // Fallback to showing the command
615
+ toolDescription = 'Update project plan';
616
+ }
617
+ }
618
+ }
619
+ else if (command.includes('apply_patch')) {
620
+ // Extract patch content and format as proper diff like Edit tool
621
+ const patchMatch = command.match(/apply_patch\s+<<'PATCH'\n([\s\S]*?)\nPATCH/);
622
+ if (patchMatch) {
623
+ const patchContent = patchMatch[1];
624
+ // Extract file information from patch
625
+ const addFileMatch = patchContent.match(/\*\*\* Add File: (.+)/);
626
+ const updateFileMatch = patchContent.match(/\*\*\* Update File: (.+)/);
627
+ const modifyFileMatch = patchContent.match(/\*\*\* Modify File: (.+)/);
628
+ if (addFileMatch) {
629
+ const fileName = addFileMatch[1];
630
+ // Extract new file content from patch (lines starting with +)
631
+ const contentLines = patchContent.split('\n').filter(line => line.startsWith('+') && !line.startsWith('+++'));
632
+ const newContent = contentLines.map(line => line.substring(1)).join('\n');
633
+ // Create proper unified diff format like Edit tool for new files
634
+ const baseName = fileName.split('/').pop() || fileName;
635
+ const newContentLines = newContent.split('\n');
636
+ const unifiedDiff = `--- /dev/null
637
+ +++ ${baseName}
638
+ @@ -0,0 +1,${newContentLines.length} @@
639
+ ${newContentLines.map(line => `+${line}`).join('\n')}`;
640
+ const fileDiff = {
641
+ fileDiff: unifiedDiff,
642
+ fileName: baseName,
643
+ originalContent: '',
644
+ newContent,
645
+ };
646
+ resultDisplay = fileDiff;
647
+ toolDescription = `Add file: ${fileName}`;
648
+ }
649
+ else if (updateFileMatch || modifyFileMatch) {
650
+ const fileName = (updateFileMatch || modifyFileMatch)[1];
651
+ // Parse the patch to extract old and new content
652
+ const patchLines = patchContent.split('\n');
653
+ let oldContent = '';
654
+ let newContent = '';
655
+ let inOldSection = false;
656
+ let inNewSection = false;
657
+ for (const line of patchLines) {
658
+ if (line.startsWith('@@')) {
659
+ // Start of a diff section
660
+ continue;
661
+ }
662
+ else if (line.startsWith('-') && !line.startsWith('---')) {
663
+ // Old content (removed lines)
664
+ oldContent += line.substring(1) + '\n';
665
+ inOldSection = true;
666
+ }
667
+ else if (line.startsWith('+') && !line.startsWith('+++')) {
668
+ // New content (added lines)
669
+ newContent += line.substring(1) + '\n';
670
+ inNewSection = true;
671
+ }
672
+ else if (!line.startsWith('***') && !line.startsWith('---') && !line.startsWith('+++')) {
673
+ // Context lines (unchanged)
674
+ if (inOldSection)
675
+ oldContent += line + '\n';
676
+ if (inNewSection)
677
+ newContent += line + '\n';
678
+ }
679
+ }
680
+ // If we couldn't parse properly, fall back to showing the raw patch
681
+ if (!oldContent && !newContent) {
682
+ // Try to reconstruct from the patch format
683
+ const beforeMatch = patchContent.match(/(?:^|\n)([^@\-+*].*)(?=\n[-+@])/gm);
684
+ const afterMatch = patchContent.match(/(?:^|\n)\+(.*)$/gm);
685
+ if (beforeMatch && afterMatch) {
686
+ oldContent = beforeMatch.join('\n');
687
+ newContent = afterMatch.map(line => line.substring(1)).join('\n');
688
+ }
689
+ }
690
+ // Create a proper unified diff format like Edit tool uses
691
+ const baseName = fileName.split('/').pop() || fileName;
692
+ const unifiedDiff = `--- ${baseName}
693
+ +++ ${baseName}
694
+ @@ -1,${oldContent.split('\n').length} +1,${newContent.split('\n').length} @@
695
+ ${oldContent.split('\n').map(line => `-${line}`).join('\n')}
696
+ ${newContent.split('\n').map(line => `+${line}`).join('\n')}`;
697
+ const fileDiff = {
698
+ fileDiff: unifiedDiff,
699
+ fileName: baseName,
700
+ originalContent: oldContent.trim(),
701
+ newContent: newContent.trim(),
702
+ };
703
+ resultDisplay = fileDiff;
704
+ toolDescription = `Update file: ${fileName}`;
705
+ }
706
+ else {
707
+ toolDescription = 'Apply patch';
708
+ // Fallback for unrecognized patch format
709
+ resultDisplay = `🔧 Applying patch
710
+
711
+ ${patchContent.substring(0, 500)}${patchContent.length > 500 ? '...' : ''}`;
712
+ }
713
+ }
714
+ }
715
+ else if (toolName.includes('edit') || toolName.includes('replace') || parsed.toolCall.name === 'Edit' || parsed.toolCall.name === 'replace') {
716
+ // For edit/replace tools, create a FileDiff object that ToolMessage expects
717
+ const args = parsed.toolCall.args;
718
+ if (args['old_string'] && args['new_string']) {
719
+ const filePath = (args['file_path'] || 'file');
720
+ const oldString = args['old_string'];
721
+ const newString = args['new_string'];
722
+ const fileName = filePath.split('/').pop() || filePath;
723
+ // Create a proper unified diff format that DiffRenderer expects (same as Edit tool)
724
+ // Use the same logic as the Edit tool in packages/core/src/tools/edit.ts
725
+ const oldLines = oldString.split('\n');
726
+ const newLines = newString.split('\n');
727
+ // Create unified diff format similar to what Edit tool generates
728
+ const diffContent = `--- ${fileName}
729
+ +++ ${fileName}
730
+ @@ -1,${oldLines.length} +1,${newLines.length} @@
731
+ ${oldLines.map(line => `-${line}`).join('\n')}
732
+ ${newLines.map(line => `+${line}`).join('\n')}`;
733
+ const fileDiff = {
734
+ fileDiff: diffContent,
735
+ fileName,
736
+ originalContent: oldString,
737
+ newContent: newString,
738
+ };
739
+ resultDisplay = fileDiff;
740
+ // Update tool description to be more specific
741
+ const instruction = args['instruction'];
742
+ toolDescription = instruction ? `${fileName}: ${instruction}` : `Replace in ${fileName}`;
743
+ }
744
+ }
745
+ else if (toolName.includes('write') || parsed.toolCall.name === 'write_file') {
746
+ // For write_file tools, create a FileDiff object that ToolMessage expects
747
+ const args = parsed.toolCall.args;
748
+ if (args['content'] && args['file_path']) {
749
+ const filePath = args['file_path'];
750
+ const newContent = args['content'];
751
+ const fileName = filePath.split('/').pop() || filePath;
752
+ // Create a unified diff format for new file (like Edit tool does for new files)
753
+ const newContentLines = newContent.split('\n');
754
+ const diffContent = `--- /dev/null
755
+ +++ ${fileName}
756
+ @@ -0,0 +1,${newContentLines.length} @@
757
+ ${newContentLines.map(line => `+${line}`).join('\n')}`;
758
+ const fileDiff = {
759
+ fileDiff: diffContent,
760
+ fileName,
761
+ originalContent: '', // Assume new file for now
762
+ newContent,
763
+ };
764
+ resultDisplay = fileDiff;
765
+ toolDescription = `Write file: ${fileName}`;
766
+ }
767
+ }
768
+ else if (toolName.includes('todowrite') || toolName.includes('todo_write')) {
769
+ // For todo tools, create a TodoResultDisplay object that ToolMessage expects
770
+ const args = parsed.toolCall.args;
771
+ if (args['todos'] && Array.isArray(args['todos'])) {
772
+ const todoDisplay = {
773
+ type: 'todo_list',
774
+ todos: args['todos'],
775
+ };
776
+ resultDisplay = todoDisplay;
777
+ }
778
+ }
779
+ else if (toolName.includes('websearch') || toolName.includes('web_search')) {
780
+ // For web search tools, create a formatted string display
781
+ const args = parsed.toolCall.args;
782
+ const query = (args['query'] || args['search_query']);
783
+ if (query) {
784
+ resultDisplay = `🔍 Searching for: "${query}"
785
+
786
+ This tool will search the web and return relevant results.`;
787
+ }
788
+ }
789
+ const toolCallDisplay = {
790
+ callId: parsed.toolCall.callId,
791
+ name: parsed.toolCall.name,
792
+ description: toolDescription,
793
+ status: ToolCallStatus.Executing, // Show as executing initially
794
+ resultDisplay,
795
+ confirmationDetails: undefined,
796
+ };
797
+ // Add the current text content to history first
798
+ if (streamingMessageBuffer.trim()) {
799
+ addItemToHistory({ type: 'gemini', text: streamingMessageBuffer }, userMessageTimestamp);
800
+ streamingMessageBuffer = '';
801
+ }
802
+ // Add the tool call as a tool group to history
803
+ const toolGroup = {
804
+ type: 'tool_group',
805
+ tools: [toolCallDisplay],
806
+ };
807
+ addItemToHistory(toolGroup, userMessageTimestamp);
808
+ // Reset the pending item for continued streaming
809
+ syncedSetPendingHistoryItem({ type: 'gemini', text: '' });
810
+ }
811
+ else if (parsed.type === 'tool_result' && parsed.toolResult) {
812
+ // Track tool result for conversation history (multi-turn context)
813
+ toolCallsHistory.push(formatToolResultForHistory(parsed.toolResult.toolId, parsed.toolResult.result));
814
+ // Update the last tool call with the result
815
+ // For now, we'll just continue with text streaming since updating
816
+ // the tool result in history is complex. The tool result will be
817
+ // visible in the agent's final output.
818
+ }
819
+ processedUpTo = cumulativeOutput.indexOf(line) + line.length + 1;
820
+ }
821
+ }
822
+ // Keep only unprocessed part of the buffer
823
+ if (processedUpTo > 0) {
824
+ cumulativeOutput = cumulativeOutput.substring(processedUpTo);
825
+ }
826
+ }
827
+ else {
828
+ // Handle other agents with cumulative/incremental detection
829
+ if (cumulativeOutput && chunk.startsWith(cumulativeOutput)) {
830
+ // This is a cumulative chunk - use it directly instead of appending
831
+ cumulativeOutput = chunk;
832
+ }
833
+ else {
834
+ // This is an incremental chunk - append it
835
+ cumulativeOutput += chunk;
836
+ }
837
+ // Parse agent output for tool calls and schedule them properly
838
+ if (scheduleToolCalls) {
839
+ const { newProcessedLength, messageContent } = parseAgentOutputForToolCalls(cumulativeOutput, lastProcessedLength, scheduleToolCalls, abortSignal, agentType);
840
+ lastProcessedLength = newProcessedLength;
841
+ // Update UI with the message content (excluding tool calls)
842
+ setPendingHistoryItem((item) => ({
843
+ type: item?.type,
844
+ text: messageContent || cumulativeOutput,
845
+ }));
846
+ }
847
+ else {
848
+ // Fallback: just show the raw output if no tool scheduler available
849
+ setPendingHistoryItem((item) => ({
850
+ type: item?.type,
851
+ text: cumulativeOutput,
852
+ }));
853
+ }
854
+ }
855
+ },
856
+ onComplete: (_, exitCode) => {
857
+ onDebugMessage(`${agentType} agent completed with exit code: ${exitCode}`);
858
+ },
859
+ onError: (_, error) => {
860
+ onDebugMessage(`${agentType} agent error: ${error.message}`);
861
+ },
862
+ abortSignal,
863
+ });
864
+ // Wait for execution to complete
865
+ const result = await executionPromise;
866
+ setPendingHistoryItem(null);
867
+ // Get final output
868
+ let finalOutput;
869
+ if (agentType === 'claude' || agentType === 'codex' || agentType === 'gemini') {
870
+ finalOutput = streamingMessageBuffer || '(Agent produced no output)';
871
+ }
872
+ else {
873
+ finalOutput = cumulativeOutput || '(Agent produced no output)';
874
+ }
875
+ if (result.error) {
876
+ // Even on error, save the partial response to conversation history
877
+ // This ensures multi-turn works even if the request failed mid-stream
878
+ if (options?.onResponseComplete && (streamingMessageBuffer.trim() || toolCallsHistory.length > 0)) {
879
+ // Build response with tool call history for multi-turn context
880
+ const toolCallsSummary = toolCallsHistory.length > 0
881
+ ? '\n\n--- Tool Activity ---\n' + toolCallsHistory.join('\n') + '\n--- End Tool Activity ---\n\n'
882
+ : '';
883
+ options.onResponseComplete(toolCallsSummary + streamingMessageBuffer.trim() + '\n\n[Response ended with error]');
884
+ }
885
+ // Clear active agent session when agent exits with error
886
+ if (clearActiveAgentSession) {
887
+ clearActiveAgentSession();
888
+ }
889
+ finalOutput = `Error: ${result.error.message}\n\n${finalOutput}`;
890
+ addItemToHistory({
891
+ type: 'error',
892
+ text: finalOutput,
893
+ }, userMessageTimestamp);
894
+ }
895
+ else if (abortSignal.aborted) {
896
+ // Even when cancelled, save the partial response to conversation history
897
+ // This ensures multi-turn works even if the request was cancelled mid-stream
898
+ if (options?.onResponseComplete && (streamingMessageBuffer.trim() || toolCallsHistory.length > 0)) {
899
+ // Build response with tool call history for multi-turn context
900
+ const toolCallsSummary = toolCallsHistory.length > 0
901
+ ? '\n\n--- Tool Activity ---\n' + toolCallsHistory.join('\n') + '\n--- End Tool Activity ---\n\n'
902
+ : '';
903
+ options.onResponseComplete(toolCallsSummary + streamingMessageBuffer.trim() + '\n\n[Response was cancelled]');
904
+ }
905
+ finalOutput = `Agent execution was cancelled.\n\n${finalOutput}`;
906
+ addItemToHistory({
907
+ type: 'error',
908
+ text: finalOutput,
909
+ }, userMessageTimestamp);
910
+ }
911
+ else if (result.exitCode !== 0 && result.exitCode !== null) {
912
+ // Even on non-zero exit, save the partial response to conversation history
913
+ // This ensures multi-turn works even if the agent exited with an error
914
+ if (options?.onResponseComplete && (streamingMessageBuffer.trim() || toolCallsHistory.length > 0)) {
915
+ // Build response with tool call history for multi-turn context
916
+ const toolCallsSummary = toolCallsHistory.length > 0
917
+ ? '\n\n--- Tool Activity ---\n' + toolCallsHistory.join('\n') + '\n--- End Tool Activity ---\n\n'
918
+ : '';
919
+ options.onResponseComplete(toolCallsSummary + streamingMessageBuffer.trim() + `\n\n[Agent exited with code ${result.exitCode}]`);
920
+ }
921
+ // Clear active agent session when agent exits with non-zero code
922
+ if (clearActiveAgentSession) {
923
+ clearActiveAgentSession();
924
+ }
925
+ finalOutput = `Agent exited with code ${result.exitCode}.\n\n${finalOutput}`;
926
+ addItemToHistory({
927
+ type: 'error',
928
+ text: finalOutput,
929
+ }, userMessageTimestamp);
930
+ }
931
+ else {
932
+ // Success - add as gemini response (only if there's actual text content)
933
+ if (finalOutput && finalOutput !== '(Agent produced no output)') {
934
+ addItemToHistory({
935
+ type: 'gemini',
936
+ text: finalOutput,
937
+ }, userMessageTimestamp);
938
+ // Call the onResponseComplete callback to update conversation history
939
+ // Include tool call history for multi-turn context
940
+ if (options?.onResponseComplete) {
941
+ const toolCallsSummary = toolCallsHistory.length > 0
942
+ ? '\n\n--- Tool Activity ---\n' + toolCallsHistory.join('\n') + '\n--- End Tool Activity ---\n\n'
943
+ : '';
944
+ options.onResponseComplete(toolCallsSummary + finalOutput);
945
+ }
946
+ }
947
+ }
948
+ resolve();
949
+ }
950
+ catch (err) {
951
+ setPendingHistoryItem(null);
952
+ const errorMessage = err instanceof Error ? err.message : String(err);
953
+ addItemToHistory({
954
+ type: 'error',
955
+ text: `An unexpected error occurred during ${agentType} agent execution: ${errorMessage}`,
956
+ }, userMessageTimestamp);
957
+ resolve();
958
+ }
959
+ };
960
+ const execPromise = new Promise((resolve) => {
961
+ executeCommand(resolve);
962
+ });
963
+ onExec(execPromise);
964
+ }, [addItemToHistory, onExec, setPendingHistoryItem, onDebugMessage, config, handleStreamingContentEvent, syncedSetPendingHistoryItem, scheduleToolCalls]);
965
+ return { executeAgent };
966
+ };
967
+ //# sourceMappingURL=useAgentCommandProcessor.js.map