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.
@@ -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
- console.log('šŸ¤” Thinking...');
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
- console.log('\nšŸ—œļø Context window approaching limit, compacting conversation...');
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
- for await (const chunk of stream) {
95
- const delta = chunk.choices[0]?.delta;
96
- if (chunk.usage) {
97
- actualUsage = chunk.usage;
98
- }
99
- if (delta?.content) {
100
- streamedContent += delta.content;
101
- assistantMessage.content = streamedContent;
102
- // Stream content to user in real-time if no tool calls are being made
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
- if (delta?.tool_calls) {
112
- hasToolCalls = true;
113
- // Initialize tool_calls array if not exists
114
- if (!assistantMessage.tool_calls) {
115
- assistantMessage.tool_calls = [];
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
- // Handle tool calls in streaming
118
- for (const toolCallDelta of delta.tool_calls) {
119
- const index = toolCallDelta.index || 0;
120
- // Ensure we have an entry at this index
121
- if (!assistantMessage.tool_calls[index]) {
122
- assistantMessage.tool_calls[index] = {
123
- id: '',
124
- type: 'function',
125
- function: { name: '', arguments: '' }
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
- if (toolCallDelta.function?.name) {
132
- assistantMessage.tool_calls[index].function.name += toolCallDelta.function.name;
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
- if (toolCallDelta.function?.arguments) {
135
- assistantMessage.tool_calls[index].function.arguments += toolCallDelta.function.arguments;
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
- console.log(`šŸ”§ Using ${message.tool_calls.length} tool(s)...`);
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 (const toolCall of message.tool_calls) {
156
- const { name, arguments: args } = toolCall.function;
157
- console.log(`šŸ› ļø ${name}`);
158
- try {
159
- const toolArgs = JSON.parse(args);
160
- const result = await handleToolCall(name, toolArgs);
161
- // Add tool result to conversation
162
- this.addMessage({
163
- role: 'tool',
164
- tool_call_id: toolCall.id,
165
- content: result
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
- // Show abbreviated result to user
168
- const lines = result.split('\\n');
169
- if (lines.length > 10) {
170
- console.log(` āœ… ${lines.slice(0, 3).join('\\n ')}\\n ... (${lines.length - 6} more lines) ...\\n ${lines.slice(-3).join('\\n ')}`);
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
- else {
173
- console.log(` āœ… ${result.slice(0, 200)}${result.length > 200 ? '...' : ''}`);
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
- catch (error) {
177
- const errorMessage = error instanceof Error ? error.message : String(error);
178
- console.log(` āŒ Error: ${errorMessage}`);
179
- // Add error result to conversation
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
- console.log('\\n'); // Add newline after streaming is complete
416
+ logger.consoleLog('\n'); // Add newline after streaming is complete
417
+ logger.debug('\\n AI response streaming completed');
196
418
  }
197
419
  else {
198
- console.log(`\\nšŸ¤– ProtoAgent: ${message.content}\\n`);
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('\\nšŸ¤– ProtoAgent: ');
438
+ process.stdout.write('\nšŸ¤– ProtoAgent: ');
210
439
  process.stdout.write(message.content);
211
- console.log('\\n');
440
+ logger.consoleLog('\n');
441
+ logger.info('šŸ¤– AI response with tool calls displayed (streaming)');
212
442
  }
213
443
  else {
214
- console.log(`\\nšŸ¤– ProtoAgent: ${message.content}\\n`);
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
- console.error('\\nāŒ API Error:', apiError?.message || 'Unknown API error');
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
- console.log('šŸ’” Authentication failed. Your API key may be invalid or expired.');
231
- console.log(' Run: protoagent config --update-key');
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
- console.log('šŸ’” Access forbidden. Check your API key permissions or billing status.');
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
- console.log('šŸ’” Bad request. There may be an issue with the request format.');
238
- console.log(' This could be a bug in ProtoAgent. Please check for updates.');
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
- console.log('šŸ’” An unexpected API error occurred. Please try again.');
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
- console.log('\\nāš ļø Maximum iteration limit reached. Task may be incomplete.');
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('\\nāŒ Error during processing:', error?.message || 'Unknown 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
- console.log('šŸ’” There seems to be an issue with your API key configuration.');
257
- console.log(' Run: protoagent config --show');
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
- console.log('šŸ’” There seems to be an issue with the selected model.');
261
- console.log(' Run: protoagent config --update-model');
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
- console.log('šŸ’” An unexpected error occurred. Please check your configuration and try again.');
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
- console.log('\\nšŸ¤– ProtoAgent: I encountered an error and cannot continue processing this request.\\n');
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
  }