@blackbox_ai/blackbox-cli 0.8.6 → 0.9.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 (135) hide show
  1. package/README.md +20 -0
  2. package/dist/package.json +2 -2
  3. package/dist/src/commands/configure/ConfigureUI.d.ts +1 -1
  4. package/dist/src/commands/configure/ConfigureUI.js +91 -22
  5. package/dist/src/commands/configure/ConfigureUI.js.map +1 -1
  6. package/dist/src/commands/configure.js +57 -16
  7. package/dist/src/commands/configure.js.map +1 -1
  8. package/dist/src/commands/shortcut.d.ts +10 -0
  9. package/dist/src/commands/shortcut.js +72 -0
  10. package/dist/src/commands/shortcut.js.map +1 -0
  11. package/dist/src/config/config.js +7 -1
  12. package/dist/src/config/config.js.map +1 -1
  13. package/dist/src/config/modelFetcher.d.ts +4 -0
  14. package/dist/src/config/modelFetcher.js +68 -22
  15. package/dist/src/config/modelFetcher.js.map +1 -1
  16. package/dist/src/config/settings.js +26 -2
  17. package/dist/src/config/settings.js.map +1 -1
  18. package/dist/src/config/settingsSchema.d.ts +29 -0
  19. package/dist/src/config/settingsSchema.js +29 -0
  20. package/dist/src/config/settingsSchema.js.map +1 -1
  21. package/dist/src/gemini.js +6 -0
  22. package/dist/src/gemini.js.map +1 -1
  23. package/dist/src/generated/git-commit.d.ts +2 -2
  24. package/dist/src/generated/git-commit.js +2 -2
  25. package/dist/src/services/BuiltinCommandLoader.js +2 -0
  26. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  27. package/dist/src/services/agentExecutor.d.ts +52 -0
  28. package/dist/src/services/agentExecutor.js +145 -0
  29. package/dist/src/services/agentExecutor.js.map +1 -0
  30. package/dist/src/ui/App.d.ts +1 -1
  31. package/dist/src/ui/App.js +47 -16
  32. package/dist/src/ui/App.js.map +1 -1
  33. package/dist/src/ui/colors.js +3 -0
  34. package/dist/src/ui/colors.js.map +1 -1
  35. package/dist/src/ui/commands/agentCommand.d.ts +7 -0
  36. package/dist/src/ui/commands/agentCommand.js +90 -0
  37. package/dist/src/ui/commands/agentCommand.js.map +1 -0
  38. package/dist/src/ui/commands/types.d.ts +12 -2
  39. package/dist/src/ui/commands/types.js.map +1 -1
  40. package/dist/src/ui/components/AgentModelSelector.d.ts +17 -0
  41. package/dist/src/ui/components/AgentModelSelector.js +41 -0
  42. package/dist/src/ui/components/AgentModelSelector.js.map +1 -0
  43. package/dist/src/ui/components/AsciiArt.d.ts +3 -3
  44. package/dist/src/ui/components/AsciiArt.js +18 -18
  45. package/dist/src/ui/components/AsciiArt.js.map +1 -1
  46. package/dist/src/ui/components/AuthDialog.js +70 -13
  47. package/dist/src/ui/components/AuthDialog.js.map +1 -1
  48. package/dist/src/ui/components/Footer.d.ts +2 -0
  49. package/dist/src/ui/components/Footer.js +2 -4
  50. package/dist/src/ui/components/Footer.js.map +1 -1
  51. package/dist/src/ui/components/Header.js +4 -1
  52. package/dist/src/ui/components/Header.js.map +1 -1
  53. package/dist/src/ui/components/InputPrompt.js +1 -1
  54. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  55. package/dist/src/ui/components/StatsDisplay.js +1 -1
  56. package/dist/src/ui/components/StatsDisplay.js.map +1 -1
  57. package/dist/src/ui/components/agents/SingleAgentDialog.d.ts +20 -0
  58. package/dist/src/ui/components/agents/SingleAgentDialog.js +150 -0
  59. package/dist/src/ui/components/agents/SingleAgentDialog.js.map +1 -0
  60. package/dist/src/ui/components/messages/ToolMessage.js +4 -4
  61. package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
  62. package/dist/src/ui/components/multiagent/MultiAgentConfigDialog.js +1 -19
  63. package/dist/src/ui/components/multiagent/MultiAgentConfigDialog.js.map +1 -1
  64. package/dist/src/ui/hooks/shellCommandProcessor.d.ts +1 -1
  65. package/dist/src/ui/hooks/shellCommandProcessor.js +13 -2
  66. package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
  67. package/dist/src/ui/hooks/slashCommandProcessor.d.ts +1 -1
  68. package/dist/src/ui/hooks/slashCommandProcessor.js +13 -29
  69. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  70. package/dist/src/ui/hooks/useAdaptiveStream.d.ts +2 -1
  71. package/dist/src/ui/hooks/useAdaptiveStream.js +2 -2
  72. package/dist/src/ui/hooks/useAdaptiveStream.js.map +1 -1
  73. package/dist/src/ui/hooks/useAgentCommandProcessor.d.ts +16 -0
  74. package/dist/src/ui/hooks/useAgentCommandProcessor.js +802 -0
  75. package/dist/src/ui/hooks/useAgentCommandProcessor.js.map +1 -0
  76. package/dist/src/ui/hooks/useEncryptedStream.js +15 -0
  77. package/dist/src/ui/hooks/useEncryptedStream.js.map +1 -1
  78. package/dist/src/ui/hooks/useGeminiStream.d.ts +2 -1
  79. package/dist/src/ui/hooks/useGeminiStream.js +95 -20
  80. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  81. package/dist/src/ui/hooks/useSingleAgentCommand.d.ts +10 -0
  82. package/dist/src/ui/hooks/useSingleAgentCommand.js +21 -0
  83. package/dist/src/ui/hooks/useSingleAgentCommand.js.map +1 -0
  84. package/dist/src/ui/themes/ansi-light.js +1 -0
  85. package/dist/src/ui/themes/ansi-light.js.map +1 -1
  86. package/dist/src/ui/themes/ansi.js +1 -0
  87. package/dist/src/ui/themes/ansi.js.map +1 -1
  88. package/dist/src/ui/themes/atom-one-dark.js +14 -13
  89. package/dist/src/ui/themes/atom-one-dark.js.map +1 -1
  90. package/dist/src/ui/themes/ayu-light.js +14 -13
  91. package/dist/src/ui/themes/ayu-light.js.map +1 -1
  92. package/dist/src/ui/themes/ayu.js +14 -13
  93. package/dist/src/ui/themes/ayu.js.map +1 -1
  94. package/dist/src/ui/themes/blackbox-dark.js +36 -31
  95. package/dist/src/ui/themes/blackbox-dark.js.map +1 -1
  96. package/dist/src/ui/themes/blackbox-light.js +41 -39
  97. package/dist/src/ui/themes/blackbox-light.js.map +1 -1
  98. package/dist/src/ui/themes/default-light.js +20 -20
  99. package/dist/src/ui/themes/default-light.js.map +1 -1
  100. package/dist/src/ui/themes/default.js +27 -27
  101. package/dist/src/ui/themes/default.js.map +1 -1
  102. package/dist/src/ui/themes/dracula.js +14 -13
  103. package/dist/src/ui/themes/dracula.js.map +1 -1
  104. package/dist/src/ui/themes/github-dark.js +14 -13
  105. package/dist/src/ui/themes/github-dark.js.map +1 -1
  106. package/dist/src/ui/themes/github-light.js +14 -13
  107. package/dist/src/ui/themes/github-light.js.map +1 -1
  108. package/dist/src/ui/themes/googlecode.js +1 -0
  109. package/dist/src/ui/themes/googlecode.js.map +1 -1
  110. package/dist/src/ui/themes/no-color.js +2 -0
  111. package/dist/src/ui/themes/no-color.js.map +1 -1
  112. package/dist/src/ui/themes/semantic-tokens.d.ts +1 -0
  113. package/dist/src/ui/themes/semantic-tokens.js +3 -0
  114. package/dist/src/ui/themes/semantic-tokens.js.map +1 -1
  115. package/dist/src/ui/themes/shades-of-purple.js +1 -0
  116. package/dist/src/ui/themes/shades-of-purple.js.map +1 -1
  117. package/dist/src/ui/themes/theme.d.ts +2 -0
  118. package/dist/src/ui/themes/theme.js +35 -28
  119. package/dist/src/ui/themes/theme.js.map +1 -1
  120. package/dist/src/ui/themes/xcode.js +1 -0
  121. package/dist/src/ui/themes/xcode.js.map +1 -1
  122. package/dist/src/ui/types.d.ts +5 -0
  123. package/dist/src/ui/utils/terminalBackgroundDetector.d.ts +19 -0
  124. package/dist/src/ui/utils/terminalBackgroundDetector.js +99 -0
  125. package/dist/src/ui/utils/terminalBackgroundDetector.js.map +1 -0
  126. package/dist/src/utils/agentCommandBuilder.d.ts +36 -0
  127. package/dist/src/utils/agentCommandBuilder.js +288 -0
  128. package/dist/src/utils/agentCommandBuilder.js.map +1 -0
  129. package/dist/src/utils/shellShortcut.d.ts +45 -0
  130. package/dist/src/utils/shellShortcut.js +221 -0
  131. package/dist/src/utils/shellShortcut.js.map +1 -0
  132. package/dist/src/utils/userStartupWarnings.js +0 -20
  133. package/dist/src/utils/userStartupWarnings.js.map +1 -1
  134. package/dist/tsconfig.tsbuildinfo +1 -1
  135. package/package.json +2 -2
@@ -0,0 +1,802 @@
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
+ * Hook to process agent execution with proper tool scheduling like GeminiClient.
319
+ * This integrates with the existing tool scheduling system for consistent UI.
320
+ */
321
+ export const useAgentCommandProcessor = (addItemToHistory, setPendingHistoryItem, onExec, onDebugMessage, config, scheduleToolCalls) => {
322
+ // Track the current pending item state with a ref for reading
323
+ const currentPendingItemRef = useRef(null);
324
+ // Wrapper to sync the ref when setPendingHistoryItem is called
325
+ const syncedSetPendingHistoryItem = useCallback((newItem) => {
326
+ currentPendingItemRef.current = newItem;
327
+ setPendingHistoryItem(newItem);
328
+ }, [setPendingHistoryItem]);
329
+ // Claude/Gemini streaming handler - exactly like Gemini's handleContentEvent
330
+ const handleStreamingContentEvent = useCallback((textDelta, currentMessageBuffer, userMessageTimestamp) => {
331
+ let newMessageBuffer = currentMessageBuffer + textDelta;
332
+ if (currentPendingItemRef.current?.type !== 'gemini' &&
333
+ currentPendingItemRef.current?.type !== 'gemini_content') {
334
+ if (currentPendingItemRef.current) {
335
+ addItemToHistory(currentPendingItemRef.current, userMessageTimestamp);
336
+ }
337
+ const newItem = { type: 'gemini', text: '' };
338
+ syncedSetPendingHistoryItem(newItem);
339
+ newMessageBuffer = textDelta;
340
+ }
341
+ // Update the existing message with accumulated content (same as Gemini)
342
+ const updatedItem = {
343
+ type: (currentPendingItemRef.current?.type || 'gemini'),
344
+ text: newMessageBuffer,
345
+ };
346
+ syncedSetPendingHistoryItem(updatedItem);
347
+ return newMessageBuffer;
348
+ }, [addItemToHistory, syncedSetPendingHistoryItem]);
349
+ const executeAgent = useCallback((agentType, model, task, abortSignal) => {
350
+ // Early return for blackbox agent - it should be handled directly by useGeminiStream
351
+ // to reuse all streaming, tools, and UI components
352
+ if (agentType === 'blackbox') {
353
+ return;
354
+ }
355
+ const userMessageTimestamp = Date.now();
356
+ // Note: User message is already added to history by the auto-routing logic in useGeminiStream
357
+ // We don't need to add it again here to avoid showing "/agent <task>"
358
+ const executeCommand = async (resolve) => {
359
+ let cumulativeOutput = '';
360
+ let lastProcessedLength = 0;
361
+ // Start with clean slate for real-time streaming (like Gemini does)
362
+ setPendingHistoryItem(null);
363
+ const executor = new AgentExecutor();
364
+ // Check if CLI is installed (since blackbox agent already returned early,
365
+ // we only get here for claude, codex, and gemini agents)
366
+ const isInstalled = await executor.checkAgentCLI(agentType);
367
+ if (!isInstalled) {
368
+ onDebugMessage(`${agentType} CLI not found. Installing...`);
369
+ // Show installation message
370
+ setPendingHistoryItem({
371
+ type: 'gemini_content',
372
+ text: `Installing ${agentType} CLI...\n\nThis may take a few moments...`,
373
+ });
374
+ const installSuccess = await executor.installAgentCLI(agentType, (chunk) => {
375
+ onDebugMessage(`Install output: ${chunk}`);
376
+ }, abortSignal);
377
+ if (!installSuccess) {
378
+ setPendingHistoryItem(null);
379
+ addItemToHistory({
380
+ type: 'error',
381
+ text: `Failed to install ${agentType} CLI. Please install it manually.`,
382
+ }, userMessageTimestamp);
383
+ resolve();
384
+ return;
385
+ }
386
+ // Clear installation message and start agent execution
387
+ setPendingHistoryItem({
388
+ type: 'gemini_content',
389
+ text: '',
390
+ });
391
+ }
392
+ try {
393
+ // Create message buffer for streaming (like Gemini does)
394
+ let streamingMessageBuffer = '';
395
+ // Execute the agent with optimized streaming output
396
+ const executionPromise = executor.executeAgent({
397
+ agentType,
398
+ model,
399
+ task,
400
+ cwd: config.getTargetDir(),
401
+ onOutput: (_, chunk) => {
402
+ // Handle Claude, Codex, and Gemini streaming differently - use direct event processing like Gemini
403
+ if (agentType === 'claude' || agentType === 'codex' || agentType === 'gemini') {
404
+ // Add chunk to cumulative output for JSON buffer management
405
+ cumulativeOutput += chunk;
406
+ // Try to parse complete JSON objects from the buffer
407
+ const jsonLines = cumulativeOutput.split('\n');
408
+ let processedUpTo = 0;
409
+ for (let i = 0; i < jsonLines.length - 1; i++) { // Skip last line as it might be incomplete
410
+ const line = jsonLines[i].trim();
411
+ if (line) {
412
+ const parsed = agentType === 'claude'
413
+ ? parseClaudeStreamingOutput(line)
414
+ : agentType === 'codex'
415
+ ? parseCodexStreamingOutput(line)
416
+ : parseGeminiStreamingOutput(line);
417
+ if (parsed.type === 'delta' && parsed.text) {
418
+ streamingMessageBuffer = handleStreamingContentEvent(parsed.text, streamingMessageBuffer, userMessageTimestamp);
419
+ }
420
+ else if (parsed.type === 'tool_use' && parsed.toolCall) {
421
+ // Create a proper tool call display object for the UI
422
+ // Format it like Gemini tools: name is the tool, description is the main argument
423
+ let toolDescription = '';
424
+ if (parsed.toolCall.args) {
425
+ // Extract the most relevant argument for the description based on tool type
426
+ const args = parsed.toolCall.args;
427
+ const toolName = parsed.toolCall.name.toLowerCase();
428
+ if (toolName.includes('websearch') || toolName.includes('web_search')) {
429
+ toolDescription = (args['query'] || args['search_query']) || 'web search';
430
+ }
431
+ else if (toolName.includes('todowrite') || toolName.includes('todo_write')) {
432
+ const todos = args['todos'];
433
+ if (todos && todos.length > 0) {
434
+ toolDescription = `${todos.length} todo${todos.length > 1 ? 's' : ''}: ${todos[0].content}${todos.length > 1 ? '...' : ''}`;
435
+ }
436
+ else {
437
+ toolDescription = 'todo list';
438
+ }
439
+ }
440
+ else if (toolName.includes('edit') || toolName.includes('write')) {
441
+ toolDescription = (args['file_path'] || args['path']) || 'file operation';
442
+ }
443
+ else if (toolName.includes('read') || toolName.includes('list')) {
444
+ toolDescription = (args['file_path'] || args['path'] || args['directory']) || 'file access';
445
+ }
446
+ else if (toolName.includes('shell') || toolName.includes('execute')) {
447
+ toolDescription = (args['command'] || args['cmd']) || 'command execution';
448
+ }
449
+ else if (args['file_path'] || args['path']) {
450
+ toolDescription = (args['file_path'] || args['path']);
451
+ }
452
+ else if (args['command']) {
453
+ toolDescription = args['command'];
454
+ }
455
+ else if (args['query']) {
456
+ toolDescription = args['query'];
457
+ }
458
+ else if (args['content']) {
459
+ toolDescription = 'content provided';
460
+ }
461
+ else {
462
+ // Fallback to first argument value
463
+ const firstValue = Object.values(args)[0];
464
+ toolDescription = typeof firstValue === 'string' ? firstValue : 'execution';
465
+ }
466
+ }
467
+ // Create appropriate result display based on tool type to match ToolMessage expectations
468
+ let resultDisplay = undefined;
469
+ const command = parsed.toolCall.args?.['command'] || '';
470
+ const toolName = parsed.toolCall.name.toLowerCase();
471
+ // Handle special Codex commands with enhanced formatting
472
+ if (command.startsWith('update_plan')) {
473
+ // Extract and format plan data
474
+ const planMatch = command.match(/update_plan\s+'(.+)'/);
475
+ if (planMatch) {
476
+ try {
477
+ const planData = JSON.parse(planMatch[1]);
478
+ if (Array.isArray(planData)) {
479
+ const todoDisplay = {
480
+ type: 'todo_list',
481
+ todos: planData.map((item, index) => ({
482
+ id: `plan-${index}`,
483
+ content: item.description || 'Unknown task',
484
+ status: item.status === 'completed' ? 'completed' :
485
+ item.status === 'in_progress' ? 'in_progress' :
486
+ 'pending'
487
+ })),
488
+ };
489
+ resultDisplay = todoDisplay;
490
+ toolDescription = `Update project plan (${planData.length} tasks)`;
491
+ }
492
+ }
493
+ catch (_e) {
494
+ // Fallback to showing the command
495
+ toolDescription = 'Update project plan';
496
+ }
497
+ }
498
+ }
499
+ else if (command.includes('apply_patch')) {
500
+ // Extract patch content and format as proper diff like Edit tool
501
+ const patchMatch = command.match(/apply_patch\s+<<'PATCH'\n([\s\S]*?)\nPATCH/);
502
+ if (patchMatch) {
503
+ const patchContent = patchMatch[1];
504
+ // Extract file information from patch
505
+ const addFileMatch = patchContent.match(/\*\*\* Add File: (.+)/);
506
+ const updateFileMatch = patchContent.match(/\*\*\* Update File: (.+)/);
507
+ const modifyFileMatch = patchContent.match(/\*\*\* Modify File: (.+)/);
508
+ if (addFileMatch) {
509
+ const fileName = addFileMatch[1];
510
+ // Extract new file content from patch (lines starting with +)
511
+ const contentLines = patchContent.split('\n').filter(line => line.startsWith('+') && !line.startsWith('+++'));
512
+ const newContent = contentLines.map(line => line.substring(1)).join('\n');
513
+ // Create proper unified diff format like Edit tool for new files
514
+ const baseName = fileName.split('/').pop() || fileName;
515
+ const newContentLines = newContent.split('\n');
516
+ const unifiedDiff = `--- /dev/null
517
+ +++ ${baseName}
518
+ @@ -0,0 +1,${newContentLines.length} @@
519
+ ${newContentLines.map(line => `+${line}`).join('\n')}`;
520
+ const fileDiff = {
521
+ fileDiff: unifiedDiff,
522
+ fileName: baseName,
523
+ originalContent: '',
524
+ newContent,
525
+ };
526
+ resultDisplay = fileDiff;
527
+ toolDescription = `Add file: ${fileName}`;
528
+ }
529
+ else if (updateFileMatch || modifyFileMatch) {
530
+ const fileName = (updateFileMatch || modifyFileMatch)[1];
531
+ // Parse the patch to extract old and new content
532
+ const patchLines = patchContent.split('\n');
533
+ let oldContent = '';
534
+ let newContent = '';
535
+ let inOldSection = false;
536
+ let inNewSection = false;
537
+ for (const line of patchLines) {
538
+ if (line.startsWith('@@')) {
539
+ // Start of a diff section
540
+ continue;
541
+ }
542
+ else if (line.startsWith('-') && !line.startsWith('---')) {
543
+ // Old content (removed lines)
544
+ oldContent += line.substring(1) + '\n';
545
+ inOldSection = true;
546
+ }
547
+ else if (line.startsWith('+') && !line.startsWith('+++')) {
548
+ // New content (added lines)
549
+ newContent += line.substring(1) + '\n';
550
+ inNewSection = true;
551
+ }
552
+ else if (!line.startsWith('***') && !line.startsWith('---') && !line.startsWith('+++')) {
553
+ // Context lines (unchanged)
554
+ if (inOldSection)
555
+ oldContent += line + '\n';
556
+ if (inNewSection)
557
+ newContent += line + '\n';
558
+ }
559
+ }
560
+ // If we couldn't parse properly, fall back to showing the raw patch
561
+ if (!oldContent && !newContent) {
562
+ // Try to reconstruct from the patch format
563
+ const beforeMatch = patchContent.match(/(?:^|\n)([^@\-+*].*)(?=\n[-+@])/gm);
564
+ const afterMatch = patchContent.match(/(?:^|\n)\+(.*)$/gm);
565
+ if (beforeMatch && afterMatch) {
566
+ oldContent = beforeMatch.join('\n');
567
+ newContent = afterMatch.map(line => line.substring(1)).join('\n');
568
+ }
569
+ }
570
+ // Create a proper unified diff format like Edit tool uses
571
+ const baseName = fileName.split('/').pop() || fileName;
572
+ const unifiedDiff = `--- ${baseName}
573
+ +++ ${baseName}
574
+ @@ -1,${oldContent.split('\n').length} +1,${newContent.split('\n').length} @@
575
+ ${oldContent.split('\n').map(line => `-${line}`).join('\n')}
576
+ ${newContent.split('\n').map(line => `+${line}`).join('\n')}`;
577
+ const fileDiff = {
578
+ fileDiff: unifiedDiff,
579
+ fileName: baseName,
580
+ originalContent: oldContent.trim(),
581
+ newContent: newContent.trim(),
582
+ };
583
+ resultDisplay = fileDiff;
584
+ toolDescription = `Update file: ${fileName}`;
585
+ }
586
+ else {
587
+ toolDescription = 'Apply patch';
588
+ // Fallback for unrecognized patch format
589
+ resultDisplay = `🔧 Applying patch
590
+
591
+ ${patchContent.substring(0, 500)}${patchContent.length > 500 ? '...' : ''}`;
592
+ }
593
+ }
594
+ }
595
+ else if (toolName.includes('edit') || toolName.includes('replace') || parsed.toolCall.name === 'Edit' || parsed.toolCall.name === 'replace') {
596
+ // For edit/replace tools, create a FileDiff object that ToolMessage expects
597
+ const args = parsed.toolCall.args;
598
+ if (args['old_string'] && args['new_string']) {
599
+ const filePath = (args['file_path'] || 'file');
600
+ const oldString = args['old_string'];
601
+ const newString = args['new_string'];
602
+ const fileName = filePath.split('/').pop() || filePath;
603
+ // Create a proper unified diff format that DiffRenderer expects (same as Edit tool)
604
+ // Use the same logic as the Edit tool in packages/core/src/tools/edit.ts
605
+ const oldLines = oldString.split('\n');
606
+ const newLines = newString.split('\n');
607
+ // Create unified diff format similar to what Edit tool generates
608
+ const diffContent = `--- ${fileName}
609
+ +++ ${fileName}
610
+ @@ -1,${oldLines.length} +1,${newLines.length} @@
611
+ ${oldLines.map(line => `-${line}`).join('\n')}
612
+ ${newLines.map(line => `+${line}`).join('\n')}`;
613
+ const fileDiff = {
614
+ fileDiff: diffContent,
615
+ fileName,
616
+ originalContent: oldString,
617
+ newContent: newString,
618
+ };
619
+ resultDisplay = fileDiff;
620
+ // Update tool description to be more specific
621
+ const instruction = args['instruction'];
622
+ toolDescription = instruction ? `${fileName}: ${instruction}` : `Replace in ${fileName}`;
623
+ }
624
+ }
625
+ else if (toolName.includes('write') || parsed.toolCall.name === 'write_file') {
626
+ // For write_file tools, create a FileDiff object that ToolMessage expects
627
+ const args = parsed.toolCall.args;
628
+ if (args['content'] && args['file_path']) {
629
+ const filePath = args['file_path'];
630
+ const newContent = args['content'];
631
+ const fileName = filePath.split('/').pop() || filePath;
632
+ // Create a unified diff format for new file (like Edit tool does for new files)
633
+ const newContentLines = newContent.split('\n');
634
+ const diffContent = `--- /dev/null
635
+ +++ ${fileName}
636
+ @@ -0,0 +1,${newContentLines.length} @@
637
+ ${newContentLines.map(line => `+${line}`).join('\n')}`;
638
+ const fileDiff = {
639
+ fileDiff: diffContent,
640
+ fileName,
641
+ originalContent: '', // Assume new file for now
642
+ newContent,
643
+ };
644
+ resultDisplay = fileDiff;
645
+ toolDescription = `Write file: ${fileName}`;
646
+ }
647
+ }
648
+ else if (toolName.includes('todowrite') || toolName.includes('todo_write')) {
649
+ // For todo tools, create a TodoResultDisplay object that ToolMessage expects
650
+ const args = parsed.toolCall.args;
651
+ if (args['todos'] && Array.isArray(args['todos'])) {
652
+ const todoDisplay = {
653
+ type: 'todo_list',
654
+ todos: args['todos'],
655
+ };
656
+ resultDisplay = todoDisplay;
657
+ }
658
+ }
659
+ else if (toolName.includes('websearch') || toolName.includes('web_search')) {
660
+ // For web search tools, create a formatted string display
661
+ const args = parsed.toolCall.args;
662
+ const query = (args['query'] || args['search_query']);
663
+ if (query) {
664
+ resultDisplay = `🔍 Searching for: "${query}"
665
+
666
+ This tool will search the web and return relevant results.`;
667
+ }
668
+ }
669
+ const toolCallDisplay = {
670
+ callId: parsed.toolCall.callId,
671
+ name: parsed.toolCall.name,
672
+ description: toolDescription,
673
+ status: ToolCallStatus.Executing, // Show as executing initially
674
+ resultDisplay,
675
+ confirmationDetails: undefined,
676
+ };
677
+ // Add the current text content to history first
678
+ if (streamingMessageBuffer.trim()) {
679
+ addItemToHistory({ type: 'gemini', text: streamingMessageBuffer }, userMessageTimestamp);
680
+ streamingMessageBuffer = '';
681
+ }
682
+ // Add the tool call as a tool group to history
683
+ const toolGroup = {
684
+ type: 'tool_group',
685
+ tools: [toolCallDisplay],
686
+ };
687
+ addItemToHistory(toolGroup, userMessageTimestamp);
688
+ // Reset the pending item for continued streaming
689
+ syncedSetPendingHistoryItem({ type: 'gemini', text: '' });
690
+ }
691
+ else if (parsed.type === 'tool_result' && parsed.toolResult) {
692
+ // Update the last tool call with the result
693
+ // For now, we'll just continue with text streaming since updating
694
+ // the tool result in history is complex. The tool result will be
695
+ // visible in the agent's final output.
696
+ }
697
+ processedUpTo = cumulativeOutput.indexOf(line) + line.length + 1;
698
+ }
699
+ }
700
+ // Keep only unprocessed part of the buffer
701
+ if (processedUpTo > 0) {
702
+ cumulativeOutput = cumulativeOutput.substring(processedUpTo);
703
+ }
704
+ }
705
+ else {
706
+ // Handle other agents with cumulative/incremental detection
707
+ if (cumulativeOutput && chunk.startsWith(cumulativeOutput)) {
708
+ // This is a cumulative chunk - use it directly instead of appending
709
+ cumulativeOutput = chunk;
710
+ }
711
+ else {
712
+ // This is an incremental chunk - append it
713
+ cumulativeOutput += chunk;
714
+ }
715
+ // Parse agent output for tool calls and schedule them properly
716
+ if (scheduleToolCalls) {
717
+ const { newProcessedLength, messageContent } = parseAgentOutputForToolCalls(cumulativeOutput, lastProcessedLength, scheduleToolCalls, abortSignal, agentType);
718
+ lastProcessedLength = newProcessedLength;
719
+ // Update UI with the message content (excluding tool calls)
720
+ setPendingHistoryItem((item) => ({
721
+ type: item?.type,
722
+ text: messageContent || cumulativeOutput,
723
+ }));
724
+ }
725
+ else {
726
+ // Fallback: just show the raw output if no tool scheduler available
727
+ setPendingHistoryItem((item) => ({
728
+ type: item?.type,
729
+ text: cumulativeOutput,
730
+ }));
731
+ }
732
+ }
733
+ },
734
+ onComplete: (_, exitCode) => {
735
+ onDebugMessage(`${agentType} agent completed with exit code: ${exitCode}`);
736
+ },
737
+ onError: (_, error) => {
738
+ onDebugMessage(`${agentType} agent error: ${error.message}`);
739
+ },
740
+ abortSignal,
741
+ });
742
+ // Wait for execution to complete
743
+ const result = await executionPromise;
744
+ setPendingHistoryItem(null);
745
+ // Get final output
746
+ let finalOutput;
747
+ if (agentType === 'claude' || agentType === 'codex' || agentType === 'gemini') {
748
+ finalOutput = streamingMessageBuffer || '(Agent produced no output)';
749
+ }
750
+ else {
751
+ finalOutput = cumulativeOutput || '(Agent produced no output)';
752
+ }
753
+ if (result.error) {
754
+ finalOutput = `Error: ${result.error.message}\n\n${finalOutput}`;
755
+ addItemToHistory({
756
+ type: 'error',
757
+ text: finalOutput,
758
+ }, userMessageTimestamp);
759
+ }
760
+ else if (abortSignal.aborted) {
761
+ finalOutput = `Agent execution was cancelled.\n\n${finalOutput}`;
762
+ addItemToHistory({
763
+ type: 'error',
764
+ text: finalOutput,
765
+ }, userMessageTimestamp);
766
+ }
767
+ else if (result.exitCode !== 0 && result.exitCode !== null) {
768
+ finalOutput = `Agent exited with code ${result.exitCode}.\n\n${finalOutput}`;
769
+ addItemToHistory({
770
+ type: 'error',
771
+ text: finalOutput,
772
+ }, userMessageTimestamp);
773
+ }
774
+ else {
775
+ // Success - add as gemini response (only if there's actual text content)
776
+ if (finalOutput && finalOutput !== '(Agent produced no output)') {
777
+ addItemToHistory({
778
+ type: 'gemini',
779
+ text: finalOutput,
780
+ }, userMessageTimestamp);
781
+ }
782
+ }
783
+ resolve();
784
+ }
785
+ catch (err) {
786
+ setPendingHistoryItem(null);
787
+ const errorMessage = err instanceof Error ? err.message : String(err);
788
+ addItemToHistory({
789
+ type: 'error',
790
+ text: `An unexpected error occurred during ${agentType} agent execution: ${errorMessage}`,
791
+ }, userMessageTimestamp);
792
+ resolve();
793
+ }
794
+ };
795
+ const execPromise = new Promise((resolve) => {
796
+ executeCommand(resolve);
797
+ });
798
+ onExec(execPromise);
799
+ }, [addItemToHistory, onExec, setPendingHistoryItem, onDebugMessage, config, handleStreamingContentEvent, syncedSetPendingHistoryItem, scheduleToolCalls]);
800
+ return { executeAgent };
801
+ };
802
+ //# sourceMappingURL=useAgentCommandProcessor.js.map