protoagent 0.0.2 ā 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agentic-loop.js +379 -88
- package/dist/config/client.js +168 -19
- package/dist/config/commands.js +44 -29
- package/dist/config/providers.js +59 -0
- package/dist/config/setup.js +55 -21
- package/dist/config/system-prompt.js +117 -21
- package/dist/index.js +84 -17
- package/dist/tools/edit-file.js +43 -6
- package/dist/tools/index.js +81 -24
- package/dist/tools/run-shell-command.js +444 -38
- package/dist/tools/task-complete.js +26 -0
- package/dist/tools/write-file.js +15 -7
- package/dist/utils/conversation-compactor.js +7 -6
- package/dist/utils/cost-tracker.js +5 -4
- package/dist/utils/enhanced-prompt.js +23 -0
- package/dist/utils/file-operations-approval.js +277 -67
- package/dist/utils/interrupt-handler.js +127 -0
- package/dist/utils/logger.js +176 -3
- package/dist/utils/user-cancellation.js +34 -0
- package/package.json +2 -2
package/dist/agentic-loop.js
CHANGED
|
@@ -4,6 +4,8 @@ import { generateSystemPrompt } from './config/system-prompt.js';
|
|
|
4
4
|
import { getModelConfig } from './config/providers.js';
|
|
5
5
|
import { estimateTokens, createUsageInfo, logUsageInfo, getContextInfo } from './utils/cost-tracker.js';
|
|
6
6
|
import { checkAndCompactIfNeeded } from './utils/conversation-compactor.js';
|
|
7
|
+
import { logger } from './utils/logger.js';
|
|
8
|
+
import { interruptHandler, UserInterruptError } from './utils/interrupt-handler.js';
|
|
7
9
|
// Create system message for ProtoAgent dynamically
|
|
8
10
|
async function createSystemMessage() {
|
|
9
11
|
const systemPrompt = await generateSystemPrompt();
|
|
@@ -27,8 +29,13 @@ export class AgenticLoop {
|
|
|
27
29
|
* Initialize the agentic loop with dynamic system message
|
|
28
30
|
*/
|
|
29
31
|
async initialize() {
|
|
32
|
+
logger.debug('š Initializing agentic loop', { component: 'AgenticLoop' });
|
|
30
33
|
this.systemMessage = await createSystemMessage();
|
|
31
34
|
this.messages = [this.systemMessage];
|
|
35
|
+
logger.debug('ā
Agentic loop initialized', {
|
|
36
|
+
component: 'AgenticLoop',
|
|
37
|
+
systemMessageLength: this.systemMessage.content?.toString().length || 0
|
|
38
|
+
});
|
|
32
39
|
}
|
|
33
40
|
/**
|
|
34
41
|
* Get the current conversation history
|
|
@@ -52,28 +59,73 @@ export class AgenticLoop {
|
|
|
52
59
|
* Process a user input and run the agentic loop
|
|
53
60
|
*/
|
|
54
61
|
async processUserInput(userInput) {
|
|
62
|
+
logger.debug('š„ Processing user input', {
|
|
63
|
+
component: 'AgenticLoop',
|
|
64
|
+
inputLength: userInput.length,
|
|
65
|
+
currentMessageCount: this.messages.length
|
|
66
|
+
});
|
|
55
67
|
try {
|
|
56
68
|
// Add user message to conversation history
|
|
69
|
+
logger.debug('ā Adding user message to conversation', { component: 'AgenticLoop' });
|
|
57
70
|
this.addMessage({
|
|
58
71
|
role: 'user',
|
|
59
72
|
content: userInput
|
|
60
73
|
});
|
|
61
|
-
|
|
74
|
+
// Show a subtle hint about Q key on first use (only if this is the first interaction)
|
|
75
|
+
if (this.messages.length === 2) { // System message + first user message
|
|
76
|
+
logger.consoleLog('š¤ Thinking... (press Q to pause)');
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
logger.consoleLog('š¤ Thinking...');
|
|
80
|
+
}
|
|
81
|
+
logger.info('š¤ AI thinking process started');
|
|
82
|
+
logger.debug('š§ Starting thinking process', {
|
|
83
|
+
component: 'AgenticLoop',
|
|
84
|
+
totalMessages: this.messages.length,
|
|
85
|
+
maxIterations: this.options.maxIterations
|
|
86
|
+
});
|
|
62
87
|
// Start the agentic loop
|
|
63
88
|
let continueProcessing = true;
|
|
64
89
|
let iterationCount = 0;
|
|
65
90
|
while (continueProcessing && iterationCount < this.options.maxIterations) {
|
|
66
91
|
iterationCount++;
|
|
92
|
+
logger.trace(`š Starting agentic loop iteration ${iterationCount}/${this.options.maxIterations}`, {
|
|
93
|
+
component: 'AgenticLoop',
|
|
94
|
+
iteration: iterationCount,
|
|
95
|
+
messageCount: this.messages.length,
|
|
96
|
+
conversationTokensApprox: this.messages.reduce((sum, msg) => sum + (typeof msg.content === 'string' ? msg.content.length / 4 : 0), 0)
|
|
97
|
+
});
|
|
67
98
|
try {
|
|
68
99
|
// Check if conversation needs compaction before making API call
|
|
100
|
+
logger.debug('š Checking context window usage', { component: 'AgenticLoop', iteration: iterationCount });
|
|
69
101
|
const modelConfig = getModelConfig(this.config.provider, this.config.model);
|
|
70
102
|
if (modelConfig) {
|
|
71
103
|
const contextInfo = getContextInfo(this.messages, modelConfig);
|
|
104
|
+
logger.debug('š Context analysis complete', {
|
|
105
|
+
component: 'AgenticLoop',
|
|
106
|
+
currentTokens: contextInfo.currentTokens,
|
|
107
|
+
maxTokens: modelConfig.contextWindow,
|
|
108
|
+
needsCompaction: contextInfo.needsCompaction,
|
|
109
|
+
percentageUsed: ((contextInfo.currentTokens / modelConfig.contextWindow) * 100).toFixed(1)
|
|
110
|
+
});
|
|
72
111
|
if (contextInfo.needsCompaction) {
|
|
73
|
-
|
|
112
|
+
logger.consoleLog('\nšļø Context window approaching limit, compacting conversation...');
|
|
113
|
+
logger.info('šļø Context compaction message displayed to user');
|
|
114
|
+
logger.debug('šļø Starting conversation compaction', { component: 'AgenticLoop' });
|
|
74
115
|
this.messages = await checkAndCompactIfNeeded(this.openaiClient, this.config.model, this.messages, modelConfig.contextWindow, contextInfo.currentTokens);
|
|
116
|
+
logger.debug('ā
Conversation compaction complete', {
|
|
117
|
+
component: 'AgenticLoop',
|
|
118
|
+
newMessageCount: this.messages.length
|
|
119
|
+
});
|
|
75
120
|
}
|
|
76
121
|
}
|
|
122
|
+
logger.debug('š Creating chat completion request', {
|
|
123
|
+
component: 'AgenticLoop',
|
|
124
|
+
model: this.config.model,
|
|
125
|
+
messageCount: this.messages.length,
|
|
126
|
+
toolsCount: tools.length,
|
|
127
|
+
streaming: true
|
|
128
|
+
});
|
|
77
129
|
// Create completion using OpenAI with built-in retry logic and cost tracking
|
|
78
130
|
const { stream, estimatedInputTokens } = await createChatCompletion(this.openaiClient, {
|
|
79
131
|
model: this.config.model,
|
|
@@ -82,7 +134,13 @@ export class AgenticLoop {
|
|
|
82
134
|
tool_choice: 'auto',
|
|
83
135
|
stream: true
|
|
84
136
|
}, this.config, this.messages);
|
|
137
|
+
logger.debug('š” Chat completion stream created', {
|
|
138
|
+
component: 'AgenticLoop',
|
|
139
|
+
estimatedInputTokens,
|
|
140
|
+
iteration: iterationCount
|
|
141
|
+
});
|
|
85
142
|
// Collect the streamed response
|
|
143
|
+
logger.trace('šØ Starting to collect streamed response', { component: 'AgenticLoop' });
|
|
86
144
|
let assistantMessage = {
|
|
87
145
|
role: 'assistant',
|
|
88
146
|
content: '',
|
|
@@ -91,53 +149,154 @@ export class AgenticLoop {
|
|
|
91
149
|
let streamedContent = '';
|
|
92
150
|
let hasToolCalls = false;
|
|
93
151
|
let actualUsage = undefined;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (this.options.streamOutput && !hasToolCalls && !delta.tool_calls) {
|
|
104
|
-
if (streamedContent === delta.content) {
|
|
105
|
-
// First content chunk
|
|
106
|
-
process.stdout.write('\\nš¤ ProtoAgent: ');
|
|
107
|
-
}
|
|
108
|
-
process.stdout.write(delta.content);
|
|
152
|
+
let chunkCount = 0;
|
|
153
|
+
// Start listening for 'Q' key interrupts during streaming
|
|
154
|
+
interruptHandler.startListening();
|
|
155
|
+
try {
|
|
156
|
+
for await (const chunk of stream) {
|
|
157
|
+
// Check for user interrupt
|
|
158
|
+
if (interruptHandler.isInterrupted()) {
|
|
159
|
+
logger.debug('User requested pause during streaming', { component: 'AgenticLoop' });
|
|
160
|
+
break;
|
|
109
161
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
162
|
+
chunkCount++;
|
|
163
|
+
const delta = chunk.choices[0]?.delta;
|
|
164
|
+
logger.trace('š¦ Processing chunk', {
|
|
165
|
+
component: 'AgenticLoop',
|
|
166
|
+
chunkNumber: chunkCount,
|
|
167
|
+
hasContent: !!delta?.content,
|
|
168
|
+
hasToolCalls: !!delta?.tool_calls,
|
|
169
|
+
finishReason: chunk.choices[0]?.finish_reason
|
|
170
|
+
});
|
|
171
|
+
if (chunk.usage) {
|
|
172
|
+
actualUsage = chunk.usage;
|
|
173
|
+
logger.trace('š Received usage data', {
|
|
174
|
+
component: 'AgenticLoop',
|
|
175
|
+
promptTokens: chunk.usage.prompt_tokens,
|
|
176
|
+
completionTokens: chunk.usage.completion_tokens,
|
|
177
|
+
totalTokens: chunk.usage.total_tokens
|
|
178
|
+
});
|
|
116
179
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
if (!
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
if (toolCallDelta.id) {
|
|
129
|
-
assistantMessage.tool_calls[index].id = toolCallDelta.id;
|
|
180
|
+
if (delta?.content) {
|
|
181
|
+
streamedContent += delta.content;
|
|
182
|
+
assistantMessage.content = streamedContent;
|
|
183
|
+
// Stream content to user in real-time if no tool calls are being made
|
|
184
|
+
if (this.options.streamOutput && !hasToolCalls && !delta.tool_calls) {
|
|
185
|
+
if (streamedContent === delta.content) {
|
|
186
|
+
// First content chunk
|
|
187
|
+
process.stdout.write('\nš¤ ProtoAgent: ');
|
|
188
|
+
}
|
|
189
|
+
process.stdout.write(delta.content);
|
|
130
190
|
}
|
|
131
|
-
|
|
132
|
-
|
|
191
|
+
}
|
|
192
|
+
if (delta?.tool_calls) {
|
|
193
|
+
hasToolCalls = true;
|
|
194
|
+
logger.trace('š§ Tool calls detected in delta', {
|
|
195
|
+
component: 'AgenticLoop',
|
|
196
|
+
toolCallsCount: delta.tool_calls.length
|
|
197
|
+
});
|
|
198
|
+
// Initialize tool_calls array if not exists
|
|
199
|
+
if (!assistantMessage.tool_calls) {
|
|
200
|
+
assistantMessage.tool_calls = [];
|
|
133
201
|
}
|
|
134
|
-
|
|
135
|
-
|
|
202
|
+
// Handle tool calls in streaming
|
|
203
|
+
for (const toolCallDelta of delta.tool_calls) {
|
|
204
|
+
const index = toolCallDelta.index || 0;
|
|
205
|
+
logger.trace('š ļø Processing tool call delta', {
|
|
206
|
+
component: 'AgenticLoop',
|
|
207
|
+
index,
|
|
208
|
+
hasId: !!toolCallDelta.id,
|
|
209
|
+
hasName: !!toolCallDelta.function?.name,
|
|
210
|
+
hasArgs: !!toolCallDelta.function?.arguments
|
|
211
|
+
});
|
|
212
|
+
// Ensure we have an entry at this index
|
|
213
|
+
if (!assistantMessage.tool_calls[index]) {
|
|
214
|
+
assistantMessage.tool_calls[index] = {
|
|
215
|
+
id: '',
|
|
216
|
+
type: 'function',
|
|
217
|
+
function: { name: '', arguments: '' }
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (toolCallDelta.id) {
|
|
221
|
+
assistantMessage.tool_calls[index].id = toolCallDelta.id;
|
|
222
|
+
}
|
|
223
|
+
if (toolCallDelta.function?.name) {
|
|
224
|
+
assistantMessage.tool_calls[index].function.name += toolCallDelta.function.name;
|
|
225
|
+
}
|
|
226
|
+
if (toolCallDelta.function?.arguments) {
|
|
227
|
+
assistantMessage.tool_calls[index].function.arguments += toolCallDelta.function.arguments;
|
|
228
|
+
}
|
|
136
229
|
}
|
|
137
230
|
}
|
|
138
231
|
}
|
|
232
|
+
logger.debug('ā
Stream processing complete', {
|
|
233
|
+
component: 'AgenticLoop',
|
|
234
|
+
totalChunks: chunkCount,
|
|
235
|
+
finalContentLength: streamedContent.length,
|
|
236
|
+
finalToolCallsCount: assistantMessage.tool_calls?.length || 0,
|
|
237
|
+
hasActualUsage: !!actualUsage
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
// Always stop listening for interrupts when streaming ends
|
|
242
|
+
interruptHandler.stopListening();
|
|
243
|
+
}
|
|
244
|
+
// Check if streaming was interrupted
|
|
245
|
+
if (interruptHandler.isInterrupted()) {
|
|
246
|
+
logger.debug('User paused during streaming', { component: 'AgenticLoop' });
|
|
247
|
+
interruptHandler.reset();
|
|
248
|
+
throw new UserInterruptError('User paused during streaming');
|
|
139
249
|
}
|
|
140
250
|
const message = assistantMessage;
|
|
251
|
+
// DEBUG: Log the complete AI response
|
|
252
|
+
logger.debug('š¤ AI Response received', {
|
|
253
|
+
component: 'AgenticLoop',
|
|
254
|
+
iteration: iterationCount,
|
|
255
|
+
responseType: message.tool_calls?.length > 0 ? 'TOOL_CALLS' : 'TEXT_RESPONSE',
|
|
256
|
+
contentLength: message.content?.length || 0,
|
|
257
|
+
contentPreview: message.content ? message.content.substring(0, 200) + (message.content.length > 200 ? '...' : '') : null,
|
|
258
|
+
rawMessage: JSON.stringify(message),
|
|
259
|
+
toolCallsCount: message.tool_calls?.length || 0,
|
|
260
|
+
toolNames: message.tool_calls?.map((tc) => tc.function.name) || [],
|
|
261
|
+
toolCallsDetails: message.tool_calls?.map((tc) => ({
|
|
262
|
+
id: tc.id,
|
|
263
|
+
name: tc.function.name,
|
|
264
|
+
argsLength: tc.function.arguments?.length || 0,
|
|
265
|
+
argsPreview: tc.function.arguments ? tc.function.arguments.substring(0, 100) + (tc.function.arguments.length > 100 ? '...' : '') : null
|
|
266
|
+
})) || []
|
|
267
|
+
});
|
|
268
|
+
// TRACE: Log the AI's decision after thinking
|
|
269
|
+
logger.trace('š§ AI thinking complete - analyzing response...', {
|
|
270
|
+
component: 'AgenticLoop',
|
|
271
|
+
iteration: iterationCount,
|
|
272
|
+
hasToolCalls: !!(message.tool_calls && message.tool_calls.length > 0),
|
|
273
|
+
hasContent: !!(message.content && message.content.trim().length > 0),
|
|
274
|
+
toolCount: message.tool_calls?.length || 0,
|
|
275
|
+
contentLength: message.content?.length || 0
|
|
276
|
+
});
|
|
277
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
278
|
+
logger.trace('š§ AI decided to use tools', {
|
|
279
|
+
component: 'AgenticLoop',
|
|
280
|
+
decision: 'TOOL_CALLS',
|
|
281
|
+
tools: message.tool_calls.map((tc) => tc.function.name).join(', '),
|
|
282
|
+
reasoning: 'AI determined that tool usage is needed to complete the task'
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
else if (message.content && message.content.trim().length > 0) {
|
|
286
|
+
logger.trace('š¬ AI decided to provide direct response', {
|
|
287
|
+
component: 'AgenticLoop',
|
|
288
|
+
decision: 'DIRECT_RESPONSE',
|
|
289
|
+
responsePreview: message.content.slice(0, 100) + (message.content.length > 100 ? '...' : ''),
|
|
290
|
+
reasoning: 'AI determined that a direct text response is sufficient'
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
logger.trace('ā ļø AI provided empty response', {
|
|
295
|
+
component: 'AgenticLoop',
|
|
296
|
+
decision: 'EMPTY_RESPONSE',
|
|
297
|
+
reasoning: 'AI returned neither tool calls nor content - this may indicate an issue'
|
|
298
|
+
});
|
|
299
|
+
}
|
|
141
300
|
// Calculate and log cost information
|
|
142
301
|
if (modelConfig) {
|
|
143
302
|
const finalInputTokens = actualUsage?.prompt_tokens ?? estimatedInputTokens;
|
|
@@ -148,43 +307,105 @@ export class AgenticLoop {
|
|
|
148
307
|
}
|
|
149
308
|
// Check if AI wants to use tools
|
|
150
309
|
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
310
|
+
logger.debug('š§ AI requested tool usage', {
|
|
311
|
+
component: 'AgenticLoop',
|
|
312
|
+
toolCount: message.tool_calls.length,
|
|
313
|
+
toolNames: message.tool_calls.map((tc) => tc.function.name)
|
|
314
|
+
});
|
|
151
315
|
// Add the AI's message (with tool calls) to conversation
|
|
152
316
|
this.addMessage(message);
|
|
153
|
-
|
|
317
|
+
logger.debug('ā AI message with tool calls added to conversation', {
|
|
318
|
+
component: 'AgenticLoop',
|
|
319
|
+
messageRole: 'assistant',
|
|
320
|
+
hasContent: !!message.content,
|
|
321
|
+
contentLength: message.content?.length || 0,
|
|
322
|
+
toolCallsCount: message.tool_calls?.length || 0,
|
|
323
|
+
conversationLength: this.messages.length
|
|
324
|
+
});
|
|
325
|
+
logger.consoleLog(`š§ Using ${message.tool_calls.length} tool(s)...`);
|
|
326
|
+
logger.info(`š§ Tool usage message displayed: ${message.tool_calls.length} tools`);
|
|
154
327
|
// Execute each tool call
|
|
155
|
-
for
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
328
|
+
// Start listening for interrupts during tool execution
|
|
329
|
+
interruptHandler.startListening();
|
|
330
|
+
try {
|
|
331
|
+
for (const toolCall of message.tool_calls) {
|
|
332
|
+
// Check for user interrupt before each tool
|
|
333
|
+
if (interruptHandler.isInterrupted()) {
|
|
334
|
+
logger.debug('User requested pause during tool execution', { component: 'AgenticLoop' });
|
|
335
|
+
interruptHandler.reset();
|
|
336
|
+
throw new UserInterruptError('User paused during tool execution');
|
|
337
|
+
}
|
|
338
|
+
const { name, arguments: args } = toolCall.function;
|
|
339
|
+
logger.debug('š ļø Executing tool', {
|
|
340
|
+
component: 'AgenticLoop',
|
|
341
|
+
toolName: name,
|
|
342
|
+
toolId: toolCall.id,
|
|
343
|
+
argsLength: args.length
|
|
166
344
|
});
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
345
|
+
logger.consoleLog(`š ļø ${name}`);
|
|
346
|
+
logger.info(`š ļø Tool execution message displayed: ${name}`);
|
|
347
|
+
try {
|
|
348
|
+
const toolArgs = JSON.parse(args);
|
|
349
|
+
logger.debug('š Tool arguments parsed', {
|
|
350
|
+
component: 'AgenticLoop',
|
|
351
|
+
toolName: name,
|
|
352
|
+
parsedArgs: Object.keys(toolArgs)
|
|
353
|
+
});
|
|
354
|
+
const startTime = Date.now();
|
|
355
|
+
const result = await handleToolCall(name, toolArgs);
|
|
356
|
+
const executionTime = Date.now() - startTime;
|
|
357
|
+
logger.debug('ā
Tool execution successful', {
|
|
358
|
+
component: 'AgenticLoop',
|
|
359
|
+
toolName: name,
|
|
360
|
+
executionTime,
|
|
361
|
+
resultLength: result.length
|
|
362
|
+
});
|
|
363
|
+
// Add tool result to conversation
|
|
364
|
+
this.addMessage({
|
|
365
|
+
role: 'tool',
|
|
366
|
+
tool_call_id: toolCall.id,
|
|
367
|
+
content: result
|
|
368
|
+
});
|
|
369
|
+
// Show abbreviated result to user
|
|
370
|
+
const lines = result.split('\n');
|
|
371
|
+
if (lines.length > 10) {
|
|
372
|
+
logger.consoleLog(` ā
${lines.slice(0, 3).join('\n ')}\n ... (${lines.length - 6} more lines) ...\n ${lines.slice(-3).join('\n ')}`);
|
|
373
|
+
logger.info(` ā
Tool result displayed (abbreviated, ${lines.length} lines)`);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
logger.consoleLog(` ā
${result.slice(0, 200)}${result.length > 200 ? '...' : ''}`);
|
|
377
|
+
logger.info(` ā
Tool result displayed (full, ${result.length} chars)`);
|
|
378
|
+
}
|
|
171
379
|
}
|
|
172
|
-
|
|
173
|
-
|
|
380
|
+
catch (error) {
|
|
381
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
382
|
+
logger.error('ā Tool execution failed', {
|
|
383
|
+
component: 'AgenticLoop',
|
|
384
|
+
toolName: name,
|
|
385
|
+
error: errorMessage
|
|
386
|
+
});
|
|
387
|
+
logger.consoleLog(` ā Error: ${errorMessage}`);
|
|
388
|
+
logger.error(` ā Tool error message displayed: ${errorMessage}`);
|
|
389
|
+
// Add error result to conversation
|
|
390
|
+
this.addMessage({
|
|
391
|
+
role: 'tool',
|
|
392
|
+
tool_call_id: toolCall.id,
|
|
393
|
+
content: `Error: ${errorMessage}`
|
|
394
|
+
});
|
|
174
395
|
}
|
|
175
396
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.addMessage({
|
|
181
|
-
role: 'tool',
|
|
182
|
-
tool_call_id: toolCall.id,
|
|
183
|
-
content: `Error: ${errorMessage}`
|
|
184
|
-
});
|
|
185
|
-
}
|
|
397
|
+
}
|
|
398
|
+
finally {
|
|
399
|
+
// Always stop listening for interrupts when tool execution ends
|
|
400
|
+
interruptHandler.stopListening();
|
|
186
401
|
}
|
|
187
402
|
// Continue the loop to let AI process tool results
|
|
403
|
+
logger.trace('š Continuing agentic loop for AI to process tool results', {
|
|
404
|
+
component: 'AgenticLoop',
|
|
405
|
+
decision: 'CONTINUE_PROCESSING',
|
|
406
|
+
reasoning: 'Tools were executed, AI needs another thinking cycle to process results',
|
|
407
|
+
nextIteration: iterationCount + 1
|
|
408
|
+
});
|
|
188
409
|
continue;
|
|
189
410
|
}
|
|
190
411
|
else {
|
|
@@ -192,78 +413,148 @@ export class AgenticLoop {
|
|
|
192
413
|
if (message.content && !hasToolCalls) {
|
|
193
414
|
// Content was already streamed to user during the loop above
|
|
194
415
|
if (this.options.streamOutput) {
|
|
195
|
-
|
|
416
|
+
logger.consoleLog('\n'); // Add newline after streaming is complete
|
|
417
|
+
logger.debug('\\n AI response streaming completed');
|
|
196
418
|
}
|
|
197
419
|
else {
|
|
198
|
-
|
|
420
|
+
logger.consoleLog(`\nš¤ ProtoAgent: ${message.content}\n`);
|
|
421
|
+
logger.info('š¤ AI response displayed to user (non-streaming)');
|
|
199
422
|
}
|
|
200
423
|
// Add AI response to conversation history
|
|
201
424
|
this.addMessage({
|
|
202
425
|
role: 'assistant',
|
|
203
426
|
content: message.content
|
|
204
427
|
});
|
|
428
|
+
logger.debug('ā AI text response added to conversation', {
|
|
429
|
+
component: 'AgenticLoop',
|
|
430
|
+
messageRole: 'assistant',
|
|
431
|
+
contentLength: message.content?.length || 0,
|
|
432
|
+
conversationLength: this.messages.length
|
|
433
|
+
});
|
|
205
434
|
}
|
|
206
435
|
else if (message.content && hasToolCalls) {
|
|
207
436
|
// AI provided content along with tool calls (rare case)
|
|
208
437
|
if (this.options.streamOutput) {
|
|
209
|
-
process.stdout.write('
|
|
438
|
+
process.stdout.write('\nš¤ ProtoAgent: ');
|
|
210
439
|
process.stdout.write(message.content);
|
|
211
|
-
|
|
440
|
+
logger.consoleLog('\n');
|
|
441
|
+
logger.info('š¤ AI response with tool calls displayed (streaming)');
|
|
212
442
|
}
|
|
213
443
|
else {
|
|
214
|
-
|
|
444
|
+
logger.consoleLog(`\nš¤ ProtoAgent: ${message.content}\n`);
|
|
445
|
+
logger.info('š¤ AI response with tool calls displayed (non-streaming)');
|
|
215
446
|
}
|
|
216
447
|
// Add AI response to conversation history
|
|
217
448
|
this.addMessage({
|
|
218
449
|
role: 'assistant',
|
|
219
450
|
content: message.content
|
|
220
451
|
});
|
|
452
|
+
logger.debug('ā AI mixed response (content + tool calls) added to conversation', {
|
|
453
|
+
component: 'AgenticLoop',
|
|
454
|
+
messageRole: 'assistant',
|
|
455
|
+
contentLength: message.content?.length || 0,
|
|
456
|
+
conversationLength: this.messages.length
|
|
457
|
+
});
|
|
221
458
|
}
|
|
459
|
+
logger.trace('ā
Agentic loop complete - stopping processing', {
|
|
460
|
+
component: 'AgenticLoop',
|
|
461
|
+
decision: 'STOP_PROCESSING',
|
|
462
|
+
reasoning: 'AI provided final response without tool calls',
|
|
463
|
+
totalIterations: iterationCount
|
|
464
|
+
});
|
|
222
465
|
continueProcessing = false;
|
|
223
466
|
}
|
|
224
467
|
}
|
|
225
468
|
catch (apiError) {
|
|
469
|
+
// Check if this is a user interrupt first
|
|
470
|
+
if (apiError instanceof UserInterruptError) {
|
|
471
|
+
logger.warn('š User pressed Q - interrupt during processing', {
|
|
472
|
+
component: 'AgenticLoop',
|
|
473
|
+
reason: apiError.message
|
|
474
|
+
});
|
|
475
|
+
console.log('\n'); // Just a clean newline
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
226
478
|
// Handle API errors that weren't caught by the retry logic
|
|
227
|
-
|
|
479
|
+
logger.trace('ā API Error occurred - stopping agentic loop', {
|
|
480
|
+
component: 'AgenticLoop',
|
|
481
|
+
decision: 'STOP_ON_API_ERROR',
|
|
482
|
+
errorStatus: apiError?.status,
|
|
483
|
+
errorMessage: apiError?.message,
|
|
484
|
+
reasoning: 'Unrecoverable API error encountered'
|
|
485
|
+
});
|
|
486
|
+
console.error('\nā API Error:', apiError?.message || 'Unknown API error');
|
|
487
|
+
logger.error('\nā API Error message displayed to user', {
|
|
488
|
+
component: 'AgenticLoop',
|
|
489
|
+
error: apiError?.message || 'Unknown API error'
|
|
490
|
+
});
|
|
228
491
|
// Check for specific error types and provide helpful messages
|
|
229
492
|
if (apiError?.status === 401) {
|
|
230
|
-
|
|
231
|
-
|
|
493
|
+
logger.consoleLog('š” Authentication failed. Your API key may be invalid or expired.');
|
|
494
|
+
logger.consoleLog(' Run: protoagent config --update-key');
|
|
495
|
+
logger.info('š” Auth error help message displayed to user');
|
|
232
496
|
}
|
|
233
497
|
else if (apiError?.status === 403) {
|
|
234
|
-
|
|
498
|
+
logger.consoleLog('š” Access forbidden. Check your API key permissions or billing status.');
|
|
499
|
+
logger.info('š” Access forbidden help message displayed to user');
|
|
235
500
|
}
|
|
236
501
|
else if (apiError?.status === 400) {
|
|
237
|
-
|
|
238
|
-
|
|
502
|
+
logger.consoleLog('š” Bad request. There may be an issue with the request format.');
|
|
503
|
+
logger.consoleLog(' This could be a bug in ProtoAgent. Please check for updates.');
|
|
504
|
+
logger.info('š” Bad request help message displayed to user');
|
|
239
505
|
}
|
|
240
506
|
else {
|
|
241
|
-
|
|
507
|
+
logger.consoleLog('š” An unexpected API error occurred. Please try again.');
|
|
508
|
+
logger.info('š” Generic API error help message displayed to user');
|
|
242
509
|
}
|
|
243
510
|
// Exit the processing loop for API errors
|
|
244
511
|
break;
|
|
245
512
|
}
|
|
246
513
|
}
|
|
247
514
|
if (iterationCount >= this.options.maxIterations) {
|
|
248
|
-
|
|
515
|
+
logger.trace('ā ļø Maximum iteration limit reached - stopping agentic loop', {
|
|
516
|
+
component: 'AgenticLoop',
|
|
517
|
+
decision: 'STOP_ON_MAX_ITERATIONS',
|
|
518
|
+
maxIterations: this.options.maxIterations,
|
|
519
|
+
reasoning: 'Reached maximum allowed iterations to prevent infinite loops'
|
|
520
|
+
});
|
|
521
|
+
logger.consoleLog('\nā ļø Maximum iteration limit reached. Task may be incomplete.');
|
|
522
|
+
logger.warn('ā ļø Max iteration warning displayed to user');
|
|
249
523
|
}
|
|
250
524
|
}
|
|
251
525
|
catch (error) {
|
|
526
|
+
// Check if this is a user interrupt
|
|
527
|
+
if (error instanceof UserInterruptError) {
|
|
528
|
+
logger.debug('š User interrupt detected - returning control to user', {
|
|
529
|
+
component: 'AgenticLoop',
|
|
530
|
+
reason: error.message
|
|
531
|
+
});
|
|
532
|
+
logger.consoleLog('\n');
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
252
535
|
// Handle general processing errors
|
|
253
|
-
console.error('
|
|
536
|
+
console.error('\nā Error during processing:', error?.message || 'Unknown error');
|
|
537
|
+
logger.error('\nā General processing error displayed to user', {
|
|
538
|
+
component: 'AgenticLoop',
|
|
539
|
+
error: error?.message || 'Unknown error'
|
|
540
|
+
});
|
|
254
541
|
// Provide helpful error messages for common issues
|
|
255
542
|
if (error?.message?.includes('API key')) {
|
|
256
|
-
|
|
257
|
-
|
|
543
|
+
logger.consoleLog('š” There seems to be an issue with your API key configuration.');
|
|
544
|
+
logger.consoleLog(' Run: protoagent config --show');
|
|
545
|
+
logger.info('š” API key error help message displayed');
|
|
258
546
|
}
|
|
259
547
|
else if (error?.message?.includes('model')) {
|
|
260
|
-
|
|
261
|
-
|
|
548
|
+
logger.consoleLog('š” There seems to be an issue with the selected model.');
|
|
549
|
+
logger.consoleLog(' Run: protoagent config --update-model');
|
|
550
|
+
logger.info('š” Model error help message displayed');
|
|
262
551
|
}
|
|
263
552
|
else {
|
|
264
|
-
|
|
553
|
+
logger.consoleLog('š” An unexpected error occurred. Please check your configuration and try again.');
|
|
554
|
+
logger.info('š” Generic error help message displayed');
|
|
265
555
|
}
|
|
266
|
-
|
|
556
|
+
logger.consoleLog('\nš¤ ProtoAgent: I encountered an error and cannot continue processing this request.\n');
|
|
557
|
+
logger.info('š¤ Error termination message displayed to user');
|
|
267
558
|
}
|
|
268
559
|
}
|
|
269
560
|
}
|