codeep 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +576 -0
  3. package/dist/api/index.d.ts +8 -0
  4. package/dist/api/index.js +421 -0
  5. package/dist/app.d.ts +2 -0
  6. package/dist/app.js +1406 -0
  7. package/dist/components/AgentProgress.d.ts +33 -0
  8. package/dist/components/AgentProgress.js +97 -0
  9. package/dist/components/Export.d.ts +8 -0
  10. package/dist/components/Export.js +27 -0
  11. package/dist/components/Help.d.ts +2 -0
  12. package/dist/components/Help.js +3 -0
  13. package/dist/components/Input.d.ts +9 -0
  14. package/dist/components/Input.js +89 -0
  15. package/dist/components/Loading.d.ts +9 -0
  16. package/dist/components/Loading.js +31 -0
  17. package/dist/components/Login.d.ts +7 -0
  18. package/dist/components/Login.js +77 -0
  19. package/dist/components/Logo.d.ts +8 -0
  20. package/dist/components/Logo.js +89 -0
  21. package/dist/components/LogoutPicker.d.ts +8 -0
  22. package/dist/components/LogoutPicker.js +61 -0
  23. package/dist/components/Message.d.ts +10 -0
  24. package/dist/components/Message.js +234 -0
  25. package/dist/components/MessageList.d.ts +10 -0
  26. package/dist/components/MessageList.js +8 -0
  27. package/dist/components/ProjectPermission.d.ts +7 -0
  28. package/dist/components/ProjectPermission.js +52 -0
  29. package/dist/components/Search.d.ts +10 -0
  30. package/dist/components/Search.js +30 -0
  31. package/dist/components/SessionPicker.d.ts +9 -0
  32. package/dist/components/SessionPicker.js +88 -0
  33. package/dist/components/Sessions.d.ts +12 -0
  34. package/dist/components/Sessions.js +102 -0
  35. package/dist/components/Settings.d.ts +7 -0
  36. package/dist/components/Settings.js +162 -0
  37. package/dist/components/Status.d.ts +2 -0
  38. package/dist/components/Status.js +12 -0
  39. package/dist/config/config.test.d.ts +1 -0
  40. package/dist/config/config.test.js +157 -0
  41. package/dist/config/index.d.ts +121 -0
  42. package/dist/config/index.js +555 -0
  43. package/dist/config/providers.d.ts +43 -0
  44. package/dist/config/providers.js +82 -0
  45. package/dist/config/providers.test.d.ts +1 -0
  46. package/dist/config/providers.test.js +132 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.js +38 -0
  49. package/dist/utils/agent.d.ts +37 -0
  50. package/dist/utils/agent.js +627 -0
  51. package/dist/utils/codeReview.d.ts +36 -0
  52. package/dist/utils/codeReview.js +390 -0
  53. package/dist/utils/context.d.ts +49 -0
  54. package/dist/utils/context.js +216 -0
  55. package/dist/utils/diffPreview.d.ts +57 -0
  56. package/dist/utils/diffPreview.js +335 -0
  57. package/dist/utils/export.d.ts +19 -0
  58. package/dist/utils/export.js +94 -0
  59. package/dist/utils/git.d.ts +85 -0
  60. package/dist/utils/git.js +399 -0
  61. package/dist/utils/git.test.d.ts +1 -0
  62. package/dist/utils/git.test.js +193 -0
  63. package/dist/utils/history.d.ts +93 -0
  64. package/dist/utils/history.js +348 -0
  65. package/dist/utils/interactive.d.ts +34 -0
  66. package/dist/utils/interactive.js +206 -0
  67. package/dist/utils/keychain.d.ts +17 -0
  68. package/dist/utils/keychain.js +160 -0
  69. package/dist/utils/learning.d.ts +89 -0
  70. package/dist/utils/learning.js +330 -0
  71. package/dist/utils/logger.d.ts +33 -0
  72. package/dist/utils/logger.js +130 -0
  73. package/dist/utils/project.d.ts +86 -0
  74. package/dist/utils/project.js +415 -0
  75. package/dist/utils/project.test.d.ts +1 -0
  76. package/dist/utils/project.test.js +212 -0
  77. package/dist/utils/ratelimit.d.ts +26 -0
  78. package/dist/utils/ratelimit.js +132 -0
  79. package/dist/utils/ratelimit.test.d.ts +1 -0
  80. package/dist/utils/ratelimit.test.js +131 -0
  81. package/dist/utils/retry.d.ts +28 -0
  82. package/dist/utils/retry.js +109 -0
  83. package/dist/utils/retry.test.d.ts +1 -0
  84. package/dist/utils/retry.test.js +163 -0
  85. package/dist/utils/search.d.ts +11 -0
  86. package/dist/utils/search.js +29 -0
  87. package/dist/utils/shell.d.ts +45 -0
  88. package/dist/utils/shell.js +242 -0
  89. package/dist/utils/skills.d.ts +144 -0
  90. package/dist/utils/skills.js +1137 -0
  91. package/dist/utils/smartContext.d.ts +29 -0
  92. package/dist/utils/smartContext.js +441 -0
  93. package/dist/utils/tools.d.ts +224 -0
  94. package/dist/utils/tools.js +731 -0
  95. package/dist/utils/update.d.ts +22 -0
  96. package/dist/utils/update.js +128 -0
  97. package/dist/utils/validation.d.ts +28 -0
  98. package/dist/utils/validation.js +141 -0
  99. package/dist/utils/validation.test.d.ts +1 -0
  100. package/dist/utils/validation.test.js +164 -0
  101. package/dist/utils/verify.d.ts +78 -0
  102. package/dist/utils/verify.js +464 -0
  103. package/package.json +68 -0
@@ -0,0 +1,627 @@
1
+ /**
2
+ * Agent loop - autonomous task execution
3
+ */
4
+ import { parseToolCalls, executeTool, createActionLog, formatToolDefinitions, getOpenAITools, getAnthropicTools, parseOpenAIToolCalls, parseAnthropicToolCalls } from './tools';
5
+ import { config, getApiKey } from '../config/index';
6
+ import { getProviderBaseUrl, getProviderAuthHeader, supportsNativeTools } from '../config/providers';
7
+ import { startSession, endSession, undoLastAction, undoAllActions, getCurrentSession, getRecentSessions, formatSession } from './history';
8
+ import { runAllVerifications, formatErrorsForAgent, hasVerificationErrors, getVerificationSummary } from './verify';
9
+ import { gatherSmartContext, formatSmartContext, extractTargetFile } from './smartContext';
10
+ const DEFAULT_OPTIONS = {
11
+ maxIterations: 20,
12
+ maxDuration: 5 * 60 * 1000, // 5 minutes
13
+ };
14
+ /**
15
+ * Generate system prompt for agent mode (used with native tool calling)
16
+ */
17
+ function getAgentSystemPrompt(projectContext) {
18
+ return `You are an AI coding agent with FULL autonomous access to this project.
19
+
20
+ ## Your Capabilities
21
+ - Read, write, edit, and delete files and directories
22
+ - Create directories with create_directory tool
23
+ - Execute shell commands (npm, git, build tools, etc.)
24
+ - Search code in the project
25
+ - List directory contents
26
+
27
+ ## IMPORTANT: Follow User Instructions Exactly
28
+ - Do EXACTLY what the user asks, nothing more
29
+ - If user says "create folder X" -> use create_directory tool to create folder X
30
+ - If user says "delete file X" -> use delete_file tool to delete file X
31
+ - Do NOT interpret or expand simple requests into complex tasks
32
+ - Simple tasks should be completed in 1-2 tool calls
33
+ - The user may write in any language - understand their request and execute it
34
+ - Tool names and parameters must ALWAYS be in English (e.g., "create_directory", not "kreiraj_direktorij")
35
+
36
+ ## Rules
37
+ 1. Always read files before editing them to understand the current content
38
+ 2. Use edit_file for modifications to existing files (preserves other content)
39
+ 3. Use write_file only for creating new files or complete overwrites
40
+ 4. Use create_directory to create new folders/directories
41
+ 5. When the task is complete, respond with a summary of what you did
42
+
43
+ ## Self-Verification
44
+ After you make changes, the system will automatically run build and tests.
45
+ If there are errors, you will receive them and must fix them.
46
+ - Read error messages carefully
47
+ - Fix the specific files and lines mentioned
48
+ - Keep trying until verification passes
49
+
50
+ ## Project Context
51
+ **Name:** ${projectContext.name}
52
+ **Type:** ${projectContext.type}
53
+
54
+ **Structure:**
55
+ \`\`\`
56
+ ${projectContext.structure}
57
+ \`\`\`
58
+
59
+ **Key Files:** ${projectContext.keyFiles.join(', ')}
60
+
61
+ You have FULL READ AND WRITE access. Use the tools to complete tasks autonomously.`;
62
+ }
63
+ /**
64
+ * Generate fallback system prompt (text-based tool calling)
65
+ */
66
+ function getFallbackSystemPrompt(projectContext) {
67
+ return `You are an AI coding agent with FULL autonomous access to this project.
68
+
69
+ ## IMPORTANT: Follow User Instructions Exactly
70
+ - Do EXACTLY what the user asks, nothing more
71
+ - If user says "create folder X" -> use create_directory tool
72
+ - If user says "delete file X" -> use delete_file tool
73
+ - Do NOT interpret or expand simple requests into complex tasks
74
+ - The user may write in any language - understand and execute
75
+ - Tool names and parameters must ALWAYS be in English
76
+
77
+ ## Available Tools
78
+ ${formatToolDefinitions()}
79
+
80
+ ## Tool Call Format
81
+ When you need to use a tool, respond with:
82
+ <tool_call>
83
+ {"tool": "tool_name", "parameters": {"param1": "value1"}}
84
+ </tool_call>
85
+
86
+ ## Examples
87
+ <tool_call>
88
+ {"tool": "create_directory", "parameters": {"path": "my-folder"}}
89
+ </tool_call>
90
+
91
+ <tool_call>
92
+ {"tool": "write_file", "parameters": {"path": "test/index.html", "content": "<!DOCTYPE html>..."}}
93
+ </tool_call>
94
+
95
+ ## Rules
96
+ 1. Use the exact format shown above
97
+ 2. Always read files before editing
98
+ 3. When done, respond WITHOUT tool calls
99
+
100
+ ## Project: ${projectContext.name} (${projectContext.type})
101
+ ${projectContext.structure}
102
+
103
+ You have FULL access. Execute tasks autonomously.`;
104
+ }
105
+ /**
106
+ * Make a chat API call for agent mode with native tool support
107
+ */
108
+ async function agentChat(messages, systemPrompt, onChunk, abortSignal) {
109
+ const protocol = config.get('protocol');
110
+ const model = config.get('model');
111
+ const apiKey = getApiKey();
112
+ const providerId = config.get('provider');
113
+ const baseUrl = getProviderBaseUrl(providerId, protocol);
114
+ const authHeader = getProviderAuthHeader(providerId, protocol);
115
+ if (!baseUrl) {
116
+ throw new Error(`Provider ${providerId} does not support ${protocol} protocol`);
117
+ }
118
+ // Check if provider supports native tools - if not, use text-based fallback directly
119
+ if (!supportsNativeTools(providerId, protocol)) {
120
+ // Provider doesn't support native tools, use text-based fallback
121
+ return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal);
122
+ }
123
+ const controller = new AbortController();
124
+ const timeout = setTimeout(() => controller.abort(), config.get('apiTimeout'));
125
+ if (abortSignal) {
126
+ abortSignal.addEventListener('abort', () => controller.abort());
127
+ }
128
+ const headers = {
129
+ 'Content-Type': 'application/json',
130
+ };
131
+ if (authHeader === 'Bearer') {
132
+ headers['Authorization'] = `Bearer ${apiKey}`;
133
+ }
134
+ else {
135
+ headers['x-api-key'] = apiKey;
136
+ }
137
+ if (protocol === 'anthropic') {
138
+ headers['anthropic-version'] = '2023-06-01';
139
+ }
140
+ try {
141
+ let endpoint;
142
+ let body;
143
+ if (protocol === 'openai') {
144
+ endpoint = `${baseUrl}/chat/completions`;
145
+ body = {
146
+ model,
147
+ messages: [
148
+ { role: 'system', content: systemPrompt },
149
+ ...messages,
150
+ ],
151
+ tools: getOpenAITools(),
152
+ tool_choice: 'auto',
153
+ temperature: config.get('temperature'),
154
+ max_tokens: config.get('maxTokens'),
155
+ };
156
+ }
157
+ else {
158
+ endpoint = `${baseUrl}/v1/messages`;
159
+ body = {
160
+ model,
161
+ system: systemPrompt,
162
+ messages: messages,
163
+ tools: getAnthropicTools(),
164
+ temperature: config.get('temperature'),
165
+ max_tokens: config.get('maxTokens'),
166
+ };
167
+ }
168
+ const response = await fetch(endpoint, {
169
+ method: 'POST',
170
+ headers,
171
+ body: JSON.stringify(body),
172
+ signal: controller.signal,
173
+ });
174
+ if (!response.ok) {
175
+ const errorText = await response.text();
176
+ // Check if error is due to tools not being supported - fallback to text mode
177
+ if (errorText.includes('tools') || errorText.includes('function') || response.status === 400) {
178
+ return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal);
179
+ }
180
+ throw new Error(`API error: ${response.status} - ${errorText}`);
181
+ }
182
+ const data = await response.json();
183
+ if (protocol === 'openai') {
184
+ const message = data.choices?.[0]?.message;
185
+ const content = message?.content || '';
186
+ const toolCalls = parseOpenAIToolCalls(message?.tool_calls || []);
187
+ if (onChunk && content) {
188
+ onChunk(content);
189
+ }
190
+ return { content, toolCalls, usedNativeTools: true };
191
+ }
192
+ else {
193
+ // Anthropic format
194
+ const contentBlocks = data.content || [];
195
+ let textContent = '';
196
+ for (const block of contentBlocks) {
197
+ if (block.type === 'text') {
198
+ textContent += block.text;
199
+ if (onChunk)
200
+ onChunk(block.text);
201
+ }
202
+ }
203
+ const toolCalls = parseAnthropicToolCalls(contentBlocks);
204
+ return { content: textContent, toolCalls, usedNativeTools: true };
205
+ }
206
+ }
207
+ catch (error) {
208
+ const err = error;
209
+ // If native tools failed, try fallback
210
+ if (err.message.includes('tools') || err.message.includes('function')) {
211
+ return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal);
212
+ }
213
+ throw error;
214
+ }
215
+ finally {
216
+ clearTimeout(timeout);
217
+ }
218
+ }
219
+ /**
220
+ * Fallback chat without native tools (text-based parsing)
221
+ */
222
+ async function agentChatFallback(messages, systemPrompt, onChunk, abortSignal) {
223
+ const protocol = config.get('protocol');
224
+ const model = config.get('model');
225
+ const apiKey = getApiKey();
226
+ const providerId = config.get('provider');
227
+ const baseUrl = getProviderBaseUrl(providerId, protocol);
228
+ const authHeader = getProviderAuthHeader(providerId, protocol);
229
+ if (!baseUrl) {
230
+ throw new Error(`Provider ${providerId} does not support ${protocol} protocol`);
231
+ }
232
+ const controller = new AbortController();
233
+ const timeout = setTimeout(() => controller.abort(), config.get('apiTimeout'));
234
+ if (abortSignal) {
235
+ abortSignal.addEventListener('abort', () => controller.abort());
236
+ }
237
+ const headers = {
238
+ 'Content-Type': 'application/json',
239
+ };
240
+ if (authHeader === 'Bearer') {
241
+ headers['Authorization'] = `Bearer ${apiKey}`;
242
+ }
243
+ else {
244
+ headers['x-api-key'] = apiKey;
245
+ }
246
+ if (protocol === 'anthropic') {
247
+ headers['anthropic-version'] = '2023-06-01';
248
+ }
249
+ // Use fallback system prompt with text-based tool format
250
+ const fallbackPrompt = systemPrompt.includes('## Available Tools')
251
+ ? systemPrompt
252
+ : systemPrompt + '\n\n' + formatToolDefinitions();
253
+ try {
254
+ let endpoint;
255
+ let body;
256
+ if (protocol === 'openai') {
257
+ endpoint = `${baseUrl}/chat/completions`;
258
+ body = {
259
+ model,
260
+ messages: [
261
+ { role: 'system', content: fallbackPrompt },
262
+ ...messages,
263
+ ],
264
+ stream: Boolean(onChunk),
265
+ temperature: config.get('temperature'),
266
+ max_tokens: config.get('maxTokens'),
267
+ };
268
+ }
269
+ else {
270
+ endpoint = `${baseUrl}/v1/messages`;
271
+ body = {
272
+ model,
273
+ messages: [
274
+ { role: 'user', content: fallbackPrompt },
275
+ { role: 'assistant', content: 'Understood. I will use the tools as specified.' },
276
+ ...messages,
277
+ ],
278
+ stream: Boolean(onChunk),
279
+ temperature: config.get('temperature'),
280
+ max_tokens: config.get('maxTokens'),
281
+ };
282
+ }
283
+ const response = await fetch(endpoint, {
284
+ method: 'POST',
285
+ headers,
286
+ body: JSON.stringify(body),
287
+ signal: controller.signal,
288
+ });
289
+ if (!response.ok) {
290
+ const error = await response.text();
291
+ throw new Error(`API error: ${response.status} - ${error}`);
292
+ }
293
+ let content;
294
+ if (onChunk && response.body) {
295
+ content = await handleStream(response.body, protocol, onChunk);
296
+ }
297
+ else {
298
+ const data = await response.json();
299
+ if (protocol === 'openai') {
300
+ content = data.choices?.[0]?.message?.content || '';
301
+ }
302
+ else {
303
+ content = data.content?.[0]?.text || '';
304
+ }
305
+ }
306
+ // Parse tool calls from text response
307
+ const toolCalls = parseToolCalls(content);
308
+ return { content, toolCalls, usedNativeTools: false };
309
+ }
310
+ finally {
311
+ clearTimeout(timeout);
312
+ }
313
+ }
314
+ /**
315
+ * Handle streaming response
316
+ */
317
+ async function handleStream(body, protocol, onChunk) {
318
+ const reader = body.getReader();
319
+ const decoder = new TextDecoder();
320
+ const chunks = [];
321
+ let buffer = '';
322
+ while (true) {
323
+ const { done, value } = await reader.read();
324
+ if (done)
325
+ break;
326
+ buffer += decoder.decode(value, { stream: true });
327
+ const lines = buffer.split('\n');
328
+ buffer = lines.pop() || '';
329
+ for (const line of lines) {
330
+ if (line.startsWith('data: ')) {
331
+ const data = line.slice(6);
332
+ if (data === '[DONE]')
333
+ continue;
334
+ try {
335
+ const parsed = JSON.parse(data);
336
+ let content;
337
+ if (protocol === 'openai') {
338
+ content = parsed.choices?.[0]?.delta?.content;
339
+ }
340
+ else if (parsed.type === 'content_block_delta') {
341
+ content = parsed.delta?.text;
342
+ }
343
+ if (content) {
344
+ chunks.push(content);
345
+ onChunk(content);
346
+ }
347
+ }
348
+ catch {
349
+ // Skip parse errors
350
+ }
351
+ }
352
+ }
353
+ }
354
+ return chunks.join('');
355
+ }
356
+ /**
357
+ * Run the agent loop
358
+ */
359
+ export async function runAgent(prompt, projectContext, options = {}) {
360
+ const opts = { ...DEFAULT_OPTIONS, ...options };
361
+ const startTime = Date.now();
362
+ const actions = [];
363
+ const messages = [];
364
+ // Start history session for undo support
365
+ const sessionId = startSession(prompt, projectContext.root || process.cwd());
366
+ // Gather smart context based on the task
367
+ const targetFile = extractTargetFile(prompt);
368
+ const smartContext = gatherSmartContext(targetFile, projectContext, prompt);
369
+ const smartContextStr = formatSmartContext(smartContext);
370
+ // Check if provider supports native tools
371
+ const protocol = config.get('protocol');
372
+ const providerId = config.get('provider');
373
+ const useNativeTools = supportsNativeTools(providerId, protocol);
374
+ // Build system prompt - use fallback format if native tools not supported
375
+ let systemPrompt = useNativeTools
376
+ ? getAgentSystemPrompt(projectContext)
377
+ : getFallbackSystemPrompt(projectContext);
378
+ if (smartContextStr) {
379
+ systemPrompt += '\n\n' + smartContextStr;
380
+ }
381
+ // Initial user message
382
+ messages.push({ role: 'user', content: prompt });
383
+ let iteration = 0;
384
+ let finalResponse = '';
385
+ let result;
386
+ try {
387
+ while (iteration < opts.maxIterations) {
388
+ // Check timeout
389
+ if (Date.now() - startTime > opts.maxDuration) {
390
+ result = {
391
+ success: false,
392
+ iterations: iteration,
393
+ actions,
394
+ finalResponse: 'Agent timed out',
395
+ error: `Exceeded maximum duration of ${opts.maxDuration / 1000} seconds`,
396
+ };
397
+ return result;
398
+ }
399
+ // Check abort signal
400
+ if (opts.abortSignal?.aborted) {
401
+ result = {
402
+ success: false,
403
+ iterations: iteration,
404
+ actions,
405
+ finalResponse: 'Agent was stopped by user',
406
+ aborted: true,
407
+ };
408
+ return result;
409
+ }
410
+ iteration++;
411
+ opts.onIteration?.(iteration, `Iteration ${iteration}/${opts.maxIterations}`);
412
+ // Get AI response
413
+ let chatResponse;
414
+ try {
415
+ chatResponse = await agentChat(messages, systemPrompt, opts.onThinking, opts.abortSignal);
416
+ }
417
+ catch (error) {
418
+ const err = error;
419
+ if (err.name === 'AbortError') {
420
+ result = {
421
+ success: false,
422
+ iterations: iteration,
423
+ actions,
424
+ finalResponse: 'Agent was stopped',
425
+ aborted: true,
426
+ };
427
+ return result;
428
+ }
429
+ throw error;
430
+ }
431
+ let { content, toolCalls, usedNativeTools } = chatResponse;
432
+ // If native tools were used but no tool calls returned, try parsing text-based tool calls
433
+ // This handles models that accept tools parameter but respond with text anyway
434
+ if (usedNativeTools && toolCalls.length === 0 && iteration === 1) {
435
+ const textToolCalls = parseToolCalls(content);
436
+ if (textToolCalls.length > 0) {
437
+ toolCalls = textToolCalls;
438
+ }
439
+ }
440
+ // If no tool calls, this is the final response
441
+ if (toolCalls.length === 0) {
442
+ // Remove <think>...</think> tags from response (some models include thinking)
443
+ finalResponse = content.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
444
+ break;
445
+ }
446
+ // Add assistant response to history
447
+ messages.push({ role: 'assistant', content });
448
+ // Execute tool calls
449
+ const toolResults = [];
450
+ for (const toolCall of toolCalls) {
451
+ opts.onToolCall?.(toolCall);
452
+ let toolResult;
453
+ if (opts.dryRun) {
454
+ // In dry run mode, simulate success
455
+ toolResult = {
456
+ success: true,
457
+ output: `[DRY RUN] Would execute: ${toolCall.tool}`,
458
+ tool: toolCall.tool,
459
+ parameters: toolCall.parameters,
460
+ };
461
+ }
462
+ else {
463
+ // Actually execute the tool
464
+ toolResult = executeTool(toolCall, projectContext.root || process.cwd());
465
+ }
466
+ opts.onToolResult?.(toolResult);
467
+ // Log action
468
+ const actionLog = createActionLog(toolCall, toolResult);
469
+ actions.push(actionLog);
470
+ // Format result for AI
471
+ if (toolResult.success) {
472
+ toolResults.push(`Tool ${toolCall.tool} succeeded:\n${toolResult.output}`);
473
+ }
474
+ else {
475
+ toolResults.push(`Tool ${toolCall.tool} failed:\n${toolResult.error || 'Unknown error'}`);
476
+ }
477
+ }
478
+ // Add tool results to messages
479
+ messages.push({
480
+ role: 'user',
481
+ content: `Tool results:\n\n${toolResults.join('\n\n')}\n\nContinue with the task. If done, provide a final summary without any tool calls.`,
482
+ });
483
+ }
484
+ // Check if we hit max iterations
485
+ if (iteration >= opts.maxIterations && !finalResponse) {
486
+ result = {
487
+ success: false,
488
+ iterations: iteration,
489
+ actions,
490
+ finalResponse: 'Agent reached maximum iterations',
491
+ error: `Exceeded maximum of ${opts.maxIterations} iterations`,
492
+ };
493
+ return result;
494
+ }
495
+ // Self-verification: Run build/test and fix errors if needed
496
+ const autoVerify = opts.autoVerify ?? config.get('agentAutoVerify');
497
+ const maxFixAttempts = opts.maxFixAttempts ?? config.get('agentMaxFixAttempts');
498
+ if (autoVerify && !opts.dryRun) {
499
+ // Check if we made any file changes worth verifying
500
+ const hasFileChanges = actions.some(a => a.type === 'write' || a.type === 'edit' || a.type === 'delete');
501
+ if (hasFileChanges) {
502
+ let fixAttempt = 0;
503
+ while (fixAttempt < maxFixAttempts) {
504
+ // Check abort signal
505
+ if (opts.abortSignal?.aborted) {
506
+ break;
507
+ }
508
+ opts.onIteration?.(iteration, `Verification attempt ${fixAttempt + 1}/${maxFixAttempts}`);
509
+ // Run verifications
510
+ const verifyResults = runAllVerifications(projectContext.root || process.cwd(), {
511
+ runBuild: true,
512
+ runTest: true,
513
+ runTypecheck: true,
514
+ runLint: false,
515
+ });
516
+ opts.onVerification?.(verifyResults);
517
+ // Check if all passed
518
+ if (!hasVerificationErrors(verifyResults)) {
519
+ const summary = getVerificationSummary(verifyResults);
520
+ finalResponse += `\n\n✓ Verification passed: ${summary.passed}/${summary.total} checks`;
521
+ break;
522
+ }
523
+ fixAttempt++;
524
+ // If we've exceeded attempts, report the errors
525
+ if (fixAttempt >= maxFixAttempts) {
526
+ const summary = getVerificationSummary(verifyResults);
527
+ finalResponse += `\n\n✗ Verification failed after ${fixAttempt} fix attempts: ${summary.errors} errors remaining`;
528
+ break;
529
+ }
530
+ // Ask agent to fix the errors
531
+ const errorMessage = formatErrorsForAgent(verifyResults);
532
+ messages.push({ role: 'assistant', content: finalResponse });
533
+ messages.push({
534
+ role: 'user',
535
+ content: `${errorMessage}\n\nFix these errors. After fixing, I will re-run verification.`
536
+ });
537
+ iteration++;
538
+ if (iteration >= opts.maxIterations) {
539
+ break;
540
+ }
541
+ // Get AI response to fix errors
542
+ try {
543
+ const fixResponse = await agentChat(messages, systemPrompt, opts.onThinking, opts.abortSignal);
544
+ const { content: fixContent, toolCalls: fixToolCalls } = fixResponse;
545
+ if (fixToolCalls.length === 0) {
546
+ // Agent gave up or thinks it's fixed
547
+ finalResponse = fixContent.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
548
+ continue; // Re-run verification
549
+ }
550
+ // Execute fix tool calls
551
+ messages.push({ role: 'assistant', content: fixContent });
552
+ const fixResults = [];
553
+ for (const toolCall of fixToolCalls) {
554
+ opts.onToolCall?.(toolCall);
555
+ const toolResult = executeTool(toolCall, projectContext.root || process.cwd());
556
+ opts.onToolResult?.(toolResult);
557
+ const actionLog = createActionLog(toolCall, toolResult);
558
+ actions.push(actionLog);
559
+ if (toolResult.success) {
560
+ fixResults.push(`Tool ${toolCall.tool} succeeded:\n${toolResult.output}`);
561
+ }
562
+ else {
563
+ fixResults.push(`Tool ${toolCall.tool} failed:\n${toolResult.error || 'Unknown error'}`);
564
+ }
565
+ }
566
+ messages.push({
567
+ role: 'user',
568
+ content: `Fix results:\n\n${fixResults.join('\n\n')}\n\nContinue fixing if needed. Re-running verification...`,
569
+ });
570
+ }
571
+ catch (error) {
572
+ // If fix attempt failed, continue to next attempt
573
+ break;
574
+ }
575
+ }
576
+ }
577
+ }
578
+ result = {
579
+ success: true,
580
+ iterations: iteration,
581
+ actions,
582
+ finalResponse,
583
+ };
584
+ return result;
585
+ }
586
+ catch (error) {
587
+ const err = error;
588
+ result = {
589
+ success: false,
590
+ iterations: iteration,
591
+ actions,
592
+ finalResponse: '',
593
+ error: err.message,
594
+ };
595
+ return result;
596
+ }
597
+ finally {
598
+ // End session and save history
599
+ endSession();
600
+ }
601
+ }
602
+ /**
603
+ * Format agent result for display
604
+ */
605
+ export function formatAgentResult(result) {
606
+ const lines = [];
607
+ if (result.success) {
608
+ lines.push(`Agent completed in ${result.iterations} iteration(s)`);
609
+ }
610
+ else if (result.aborted) {
611
+ lines.push('Agent was stopped by user');
612
+ }
613
+ else {
614
+ lines.push(`Agent failed: ${result.error}`);
615
+ }
616
+ if (result.actions.length > 0) {
617
+ lines.push('');
618
+ lines.push('Actions performed:');
619
+ for (const action of result.actions) {
620
+ const status = action.result === 'success' ? '✓' : '✗';
621
+ lines.push(` ${status} ${action.type}: ${action.target}`);
622
+ }
623
+ }
624
+ return lines.join('\n');
625
+ }
626
+ // Re-export history functions for undo support
627
+ export { undoLastAction, undoAllActions, getCurrentSession, getRecentSessions, formatSession };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Code Review Mode - AI-powered code review
3
+ */
4
+ import { ProjectContext } from './project';
5
+ export interface ReviewIssue {
6
+ file: string;
7
+ line?: number;
8
+ severity: 'error' | 'warning' | 'info' | 'suggestion';
9
+ category: ReviewCategory;
10
+ message: string;
11
+ suggestion?: string;
12
+ }
13
+ export type ReviewCategory = 'security' | 'performance' | 'maintainability' | 'bug' | 'style' | 'types' | 'best-practice' | 'documentation';
14
+ export interface ReviewResult {
15
+ files: string[];
16
+ issues: ReviewIssue[];
17
+ summary: ReviewSummary;
18
+ score: number;
19
+ }
20
+ export interface ReviewSummary {
21
+ totalIssues: number;
22
+ byCategory: Record<ReviewCategory, number>;
23
+ bySeverity: Record<string, number>;
24
+ }
25
+ /**
26
+ * Perform code review
27
+ */
28
+ export declare function performCodeReview(projectContext: ProjectContext, specificFiles?: string[]): ReviewResult;
29
+ /**
30
+ * Format review result for display
31
+ */
32
+ export declare function formatReviewResult(result: ReviewResult): string;
33
+ /**
34
+ * Get review prompt for AI-enhanced review
35
+ */
36
+ export declare function getReviewSystemPrompt(result: ReviewResult): string;