@blackbox_ai/blackbox-cli 0.8.3 → 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.
- package/README.md +20 -0
- package/dist/package.json +2 -2
- package/dist/src/commands/configure/ConfigureUI.d.ts +1 -1
- package/dist/src/commands/configure/ConfigureUI.js +130 -26
- package/dist/src/commands/configure/ConfigureUI.js.map +1 -1
- package/dist/src/commands/configure.js +57 -16
- package/dist/src/commands/configure.js.map +1 -1
- package/dist/src/commands/shortcut.d.ts +10 -0
- package/dist/src/commands/shortcut.js +72 -0
- package/dist/src/commands/shortcut.js.map +1 -0
- package/dist/src/config/config.js +7 -1
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/modelFetcher.d.ts +4 -0
- package/dist/src/config/modelFetcher.js +68 -22
- package/dist/src/config/modelFetcher.js.map +1 -1
- package/dist/src/config/settings.js +26 -2
- package/dist/src/config/settings.js.map +1 -1
- package/dist/src/config/settingsSchema.d.ts +29 -0
- package/dist/src/config/settingsSchema.js +29 -0
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/gemini.js +6 -0
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/services/BuiltinCommandLoader.js +2 -0
- package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
- package/dist/src/services/agentExecutor.d.ts +52 -0
- package/dist/src/services/agentExecutor.js +145 -0
- package/dist/src/services/agentExecutor.js.map +1 -0
- package/dist/src/ui/App.d.ts +1 -1
- package/dist/src/ui/App.js +47 -16
- package/dist/src/ui/App.js.map +1 -1
- package/dist/src/ui/colors.js +3 -0
- package/dist/src/ui/colors.js.map +1 -1
- package/dist/src/ui/commands/agentCommand.d.ts +7 -0
- package/dist/src/ui/commands/agentCommand.js +90 -0
- package/dist/src/ui/commands/agentCommand.js.map +1 -0
- package/dist/src/ui/commands/types.d.ts +12 -2
- package/dist/src/ui/commands/types.js.map +1 -1
- package/dist/src/ui/components/AgentModelSelector.d.ts +17 -0
- package/dist/src/ui/components/AgentModelSelector.js +41 -0
- package/dist/src/ui/components/AgentModelSelector.js.map +1 -0
- package/dist/src/ui/components/AsciiArt.d.ts +3 -3
- package/dist/src/ui/components/AsciiArt.js +18 -18
- package/dist/src/ui/components/AsciiArt.js.map +1 -1
- package/dist/src/ui/components/AuthDialog.js +70 -13
- package/dist/src/ui/components/AuthDialog.js.map +1 -1
- package/dist/src/ui/components/Footer.d.ts +2 -0
- package/dist/src/ui/components/Footer.js +2 -4
- package/dist/src/ui/components/Footer.js.map +1 -1
- package/dist/src/ui/components/Header.js +4 -1
- package/dist/src/ui/components/Header.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.js +1 -1
- package/dist/src/ui/components/InputPrompt.js.map +1 -1
- package/dist/src/ui/components/StatsDisplay.js +1 -1
- package/dist/src/ui/components/StatsDisplay.js.map +1 -1
- package/dist/src/ui/components/agents/SingleAgentDialog.d.ts +20 -0
- package/dist/src/ui/components/agents/SingleAgentDialog.js +150 -0
- package/dist/src/ui/components/agents/SingleAgentDialog.js.map +1 -0
- package/dist/src/ui/components/messages/ToolMessage.js +4 -4
- package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
- package/dist/src/ui/components/multiagent/MultiAgentConfigDialog.js +1 -19
- package/dist/src/ui/components/multiagent/MultiAgentConfigDialog.js.map +1 -1
- package/dist/src/ui/hooks/shellCommandProcessor.d.ts +1 -1
- package/dist/src/ui/hooks/shellCommandProcessor.js +13 -2
- package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/slashCommandProcessor.d.ts +1 -1
- package/dist/src/ui/hooks/slashCommandProcessor.js +13 -29
- package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/useAdaptiveStream.d.ts +2 -1
- package/dist/src/ui/hooks/useAdaptiveStream.js +2 -2
- package/dist/src/ui/hooks/useAdaptiveStream.js.map +1 -1
- package/dist/src/ui/hooks/useAgentCommandProcessor.d.ts +16 -0
- package/dist/src/ui/hooks/useAgentCommandProcessor.js +802 -0
- package/dist/src/ui/hooks/useAgentCommandProcessor.js.map +1 -0
- package/dist/src/ui/hooks/useEncryptedStream.js +15 -0
- package/dist/src/ui/hooks/useEncryptedStream.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.d.ts +2 -1
- package/dist/src/ui/hooks/useGeminiStream.js +95 -20
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useSingleAgentCommand.d.ts +10 -0
- package/dist/src/ui/hooks/useSingleAgentCommand.js +21 -0
- package/dist/src/ui/hooks/useSingleAgentCommand.js.map +1 -0
- package/dist/src/ui/themes/ansi-light.js +1 -0
- package/dist/src/ui/themes/ansi-light.js.map +1 -1
- package/dist/src/ui/themes/ansi.js +1 -0
- package/dist/src/ui/themes/ansi.js.map +1 -1
- package/dist/src/ui/themes/atom-one-dark.js +14 -13
- package/dist/src/ui/themes/atom-one-dark.js.map +1 -1
- package/dist/src/ui/themes/ayu-light.js +14 -13
- package/dist/src/ui/themes/ayu-light.js.map +1 -1
- package/dist/src/ui/themes/ayu.js +14 -13
- package/dist/src/ui/themes/ayu.js.map +1 -1
- package/dist/src/ui/themes/blackbox-dark.js +36 -31
- package/dist/src/ui/themes/blackbox-dark.js.map +1 -1
- package/dist/src/ui/themes/blackbox-light.js +41 -39
- package/dist/src/ui/themes/blackbox-light.js.map +1 -1
- package/dist/src/ui/themes/default-light.js +20 -20
- package/dist/src/ui/themes/default-light.js.map +1 -1
- package/dist/src/ui/themes/default.js +27 -27
- package/dist/src/ui/themes/default.js.map +1 -1
- package/dist/src/ui/themes/dracula.js +14 -13
- package/dist/src/ui/themes/dracula.js.map +1 -1
- package/dist/src/ui/themes/github-dark.js +14 -13
- package/dist/src/ui/themes/github-dark.js.map +1 -1
- package/dist/src/ui/themes/github-light.js +14 -13
- package/dist/src/ui/themes/github-light.js.map +1 -1
- package/dist/src/ui/themes/googlecode.js +1 -0
- package/dist/src/ui/themes/googlecode.js.map +1 -1
- package/dist/src/ui/themes/no-color.js +2 -0
- package/dist/src/ui/themes/no-color.js.map +1 -1
- package/dist/src/ui/themes/semantic-tokens.d.ts +1 -0
- package/dist/src/ui/themes/semantic-tokens.js +3 -0
- package/dist/src/ui/themes/semantic-tokens.js.map +1 -1
- package/dist/src/ui/themes/shades-of-purple.js +1 -0
- package/dist/src/ui/themes/shades-of-purple.js.map +1 -1
- package/dist/src/ui/themes/theme.d.ts +2 -0
- package/dist/src/ui/themes/theme.js +35 -28
- package/dist/src/ui/themes/theme.js.map +1 -1
- package/dist/src/ui/themes/xcode.js +1 -0
- package/dist/src/ui/themes/xcode.js.map +1 -1
- package/dist/src/ui/types.d.ts +5 -0
- package/dist/src/ui/utils/terminalBackgroundDetector.d.ts +19 -0
- package/dist/src/ui/utils/terminalBackgroundDetector.js +99 -0
- package/dist/src/ui/utils/terminalBackgroundDetector.js.map +1 -0
- package/dist/src/utils/agentCommandBuilder.d.ts +36 -0
- package/dist/src/utils/agentCommandBuilder.js +288 -0
- package/dist/src/utils/agentCommandBuilder.js.map +1 -0
- package/dist/src/utils/shellShortcut.d.ts +45 -0
- package/dist/src/utils/shellShortcut.js +221 -0
- package/dist/src/utils/shellShortcut.js.map +1 -0
- package/dist/src/utils/userStartupWarnings.js +0 -20
- package/dist/src/utils/userStartupWarnings.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- 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
|