vibecodingmachine-cli 2026.1.3-2209 → 2026.1.23-1010
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/__tests__/antigravity-js-handler.test.js +23 -0
- package/__tests__/provider-manager.test.js +84 -0
- package/__tests__/provider-rate-cache.test.js +27 -0
- package/bin/vibecodingmachine.js +8 -0
- package/package.json +2 -2
- package/reset_provider_order.js +21 -0
- package/scripts/convert-requirements.js +35 -0
- package/scripts/debug-parse.js +24 -0
- package/src/commands/auto-direct.js +679 -120
- package/src/commands/auto.js +200 -45
- package/src/commands/ide.js +108 -3
- package/src/commands/requirements-remote.js +10 -1
- package/src/commands/status.js +39 -1
- package/src/utils/antigravity-js-handler.js +13 -4
- package/src/utils/auth.js +37 -13
- package/src/utils/compliance-check.js +10 -0
- package/src/utils/config.js +29 -1
- package/src/utils/date-formatter.js +44 -0
- package/src/utils/interactive.js +1006 -537
- package/src/utils/kiro-js-handler.js +188 -0
- package/src/utils/provider-rate-cache.js +31 -0
- package/src/utils/provider-registry.js +42 -1
- package/src/utils/requirements-converter.js +107 -0
- package/src/utils/requirements-parser.js +144 -0
- package/tests/antigravity-js-handler.test.js +23 -0
- package/tests/integration/health-tracking.integration.test.js +284 -0
- package/tests/provider-manager.test.js +92 -0
- package/tests/rate-limit-display.test.js +44 -0
- package/tests/requirements-bullet-parsing.test.js +15 -0
- package/tests/requirements-converter.test.js +42 -0
- package/tests/requirements-heading-count.test.js +27 -0
- package/tests/requirements-legacy-parsing.test.js +15 -0
- package/tests/requirements-parse-integration.test.js +44 -0
- package/tests/wait-for-ide-completion.test.js +56 -0
- package/tests/wait-for-ide-quota-detection-cursor-screenshot.test.js +61 -0
- package/tests/wait-for-ide-quota-detection-cursor.test.js +60 -0
- package/tests/wait-for-ide-quota-detection-negative.test.js +45 -0
- package/tests/wait-for-ide-quota-detection.test.js +59 -0
- package/verify_fix.js +36 -0
- package/verify_ui.js +38 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
|
-
const { DirectLLMManager, AppleScriptManager, t, detectLocale, setLocale } = require('vibecodingmachine-core');
|
|
8
|
+
const { DirectLLMManager, AppleScriptManager, QuotaDetector, IDEHealthTracker, t, detectLocale, setLocale } = require('vibecodingmachine-core');
|
|
9
9
|
|
|
10
10
|
// Initialize locale detection for auto mode
|
|
11
11
|
const detectedLocale = detectLocale();
|
|
@@ -17,11 +17,13 @@ const path = require('path');
|
|
|
17
17
|
const { spawn } = require('child_process');
|
|
18
18
|
const chokidar = require('chokidar');
|
|
19
19
|
const stringWidth = require('string-width');
|
|
20
|
-
const { getProviderPreferences, getProviderDefinition } = require('../utils/provider-registry');
|
|
20
|
+
const { getProviderPreferences, getProviderDefinition, saveProviderPreferences } = require('../utils/provider-registry');
|
|
21
21
|
const { createKeyboardHandler } = require('../utils/keyboard-handler');
|
|
22
22
|
const logger = require('../utils/logger');
|
|
23
23
|
const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
|
|
24
24
|
const { checkAntigravityRateLimit, handleAntigravityRateLimit } = require('../utils/antigravity-js-handler');
|
|
25
|
+
const { checkKiroRateLimit, handleKiroRateLimit } = require('../utils/kiro-js-handler');
|
|
26
|
+
const { startAutoMode, stopAutoMode, updateAutoModeStatus } = require('../utils/auto-mode');
|
|
25
27
|
|
|
26
28
|
// Status management will use in-process tracking instead of external file
|
|
27
29
|
const CLI_ENTRY_POINT = path.join(__dirname, '../../bin/vibecodingmachine.js');
|
|
@@ -29,6 +31,16 @@ const CLI_ENTRY_POINT = path.join(__dirname, '../../bin/vibecodingmachine.js');
|
|
|
29
31
|
// CRITICAL: Shared ProviderManager instance to track rate limits across all function calls
|
|
30
32
|
const sharedProviderManager = new ProviderManager();
|
|
31
33
|
|
|
34
|
+
// CRITICAL: Shared IDEHealthTracker instance to track IDE reliability across all iterations
|
|
35
|
+
const sharedHealthTracker = new IDEHealthTracker();
|
|
36
|
+
|
|
37
|
+
// Listen for consecutive failures to warn user
|
|
38
|
+
sharedHealthTracker.on('consecutive-failures', ({ ideId, count, lastError }) => {
|
|
39
|
+
console.log(chalk.yellow(`\n⚠️ WARNING: ${ideId} has failed ${count} times consecutively`));
|
|
40
|
+
console.log(chalk.yellow(` Last error: ${lastError}`));
|
|
41
|
+
console.log(chalk.yellow(` Consider switching to a different IDE\n`));
|
|
42
|
+
});
|
|
43
|
+
|
|
32
44
|
// Configured stages (will be loaded in handleAutoStart)
|
|
33
45
|
let configuredStages = DEFAULT_STAGES;
|
|
34
46
|
|
|
@@ -45,6 +57,22 @@ function getTimestamp() {
|
|
|
45
57
|
}) + ' MST';
|
|
46
58
|
}
|
|
47
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Get a human-friendly timestamp for log prefixes that includes date, time, and timezone
|
|
62
|
+
* Example: "2025-01-02 3:45 PM MST"
|
|
63
|
+
*/
|
|
64
|
+
function getLogTimestamp(date = new Date()) {
|
|
65
|
+
const datePart = date.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
66
|
+
const timePart = date.toLocaleTimeString('en-US', {
|
|
67
|
+
hour: 'numeric',
|
|
68
|
+
minute: '2-digit',
|
|
69
|
+
hour12: true,
|
|
70
|
+
timeZone: 'America/Denver',
|
|
71
|
+
timeZoneName: 'short'
|
|
72
|
+
});
|
|
73
|
+
return `${datePart} ${timePart}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
48
76
|
/**
|
|
49
77
|
* Translate workflow stage names
|
|
50
78
|
*/
|
|
@@ -378,7 +406,7 @@ async function moveRequirementToVerify(repoPath, requirementText) {
|
|
|
378
406
|
|
|
379
407
|
const content = await fs.readFile(reqPath, 'utf8');
|
|
380
408
|
const lines = content.split('\n');
|
|
381
|
-
|
|
409
|
+
|
|
382
410
|
// Get computer filter from config
|
|
383
411
|
const { getComputerFilter } = require('../utils/config');
|
|
384
412
|
const computerFilter = await getComputerFilter();
|
|
@@ -401,95 +429,94 @@ async function moveRequirementToVerify(repoPath, requirementText) {
|
|
|
401
429
|
continue;
|
|
402
430
|
}
|
|
403
431
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
432
|
+
// Check if we're leaving TODO section
|
|
433
|
+
if (inTodoSection && trimmed.startsWith('##') && !trimmed.startsWith('###') && !trimmed.includes('Requirements not yet completed')) {
|
|
434
|
+
inTodoSection = false;
|
|
435
|
+
}
|
|
408
436
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
437
|
+
// Only look for requirements in TODO section
|
|
438
|
+
if (inTodoSection && trimmed.startsWith('###')) {
|
|
439
|
+
const title = trimmed.replace(/^###\s*/, '').trim();
|
|
440
|
+
if (title) {
|
|
441
|
+
// Try multiple matching strategies
|
|
442
|
+
const normalizedTitle = title.trim();
|
|
415
443
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
444
|
+
// Exact match
|
|
445
|
+
if (normalizedTitle === normalizedRequirement) {
|
|
446
|
+
requirementStartIndex = i;
|
|
447
|
+
}
|
|
448
|
+
// Check if either contains the other (for partial matches)
|
|
449
|
+
else if (normalizedTitle.includes(normalizedRequirement) || normalizedRequirement.includes(normalizedTitle)) {
|
|
450
|
+
requirementStartIndex = i;
|
|
451
|
+
}
|
|
452
|
+
// Check snippet matches
|
|
453
|
+
else if (normalizedTitle.includes(snippet) || snippet.includes(normalizedTitle.substring(0, 80))) {
|
|
454
|
+
requirementStartIndex = i;
|
|
455
|
+
}
|
|
428
456
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
457
|
+
if (requirementStartIndex !== -1) {
|
|
458
|
+
// Find the end of this requirement (next ### or ## header)
|
|
459
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
460
|
+
const nextLine = lines[j].trim();
|
|
461
|
+
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
462
|
+
requirementEndIndex = j;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (requirementEndIndex === -1) {
|
|
467
|
+
requirementEndIndex = lines.length;
|
|
468
|
+
}
|
|
435
469
|
break;
|
|
436
470
|
}
|
|
437
471
|
}
|
|
438
|
-
if (requirementEndIndex === -1) {
|
|
439
|
-
requirementEndIndex = lines.length;
|
|
440
|
-
}
|
|
441
|
-
break;
|
|
442
472
|
}
|
|
443
473
|
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
474
|
|
|
447
|
-
if (requirementStartIndex === -1) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
475
|
+
if (requirementStartIndex === -1) {
|
|
476
|
+
console.log(chalk.yellow(`⚠️ ${t('auto.direct.requirement.not.found.todo', { requirement: requirementText.substring(0, 60) + '...' })}`));
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
451
479
|
|
|
452
|
-
// Extract the entire requirement block
|
|
453
|
-
const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
|
|
480
|
+
// Extract the entire requirement block
|
|
481
|
+
const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
|
|
454
482
|
|
|
455
|
-
// Remove the requirement from its current location
|
|
456
|
-
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
|
|
483
|
+
// Remove the requirement from its current location
|
|
484
|
+
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
|
|
457
485
|
|
|
458
|
-
// Check if there are any more requirements in TODO section after removal
|
|
459
|
-
let hasMoreTodoRequirements = false;
|
|
460
|
-
inTodoSection = false; // Reset the existing variable
|
|
461
|
-
for (let i = 0; i < lines.length; i++) {
|
|
462
|
-
|
|
486
|
+
// Check if there are any more requirements in TODO section after removal
|
|
487
|
+
let hasMoreTodoRequirements = false;
|
|
488
|
+
inTodoSection = false; // Reset the existing variable
|
|
489
|
+
for (let i = 0; i < lines.length; i++) {
|
|
490
|
+
const line = lines[i].trim();
|
|
463
491
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
492
|
+
// Check if we're entering TODO section
|
|
493
|
+
if (line.startsWith('##') && line.includes('Requirements not yet completed')) {
|
|
494
|
+
inTodoSection = true;
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
469
497
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
498
|
+
// Check if we're leaving TODO section
|
|
499
|
+
if (inTodoSection && line.startsWith('##') && !line.startsWith('###') && !line.includes('Requirements not yet completed')) {
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
474
502
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
503
|
+
// Check if we found a requirement in TODO section
|
|
504
|
+
if (inTodoSection && line.startsWith('###')) {
|
|
505
|
+
const title = line.replace(/^###\s*/, '').trim();
|
|
506
|
+
if (title && title.includes(computerFilter)) {
|
|
507
|
+
hasMoreTodoRequirements = true;
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
481
511
|
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// If no more TODO requirements, log message
|
|
486
|
-
if (!hasMoreTodoRequirements) {
|
|
487
|
-
console.log(chalk.green(`🎉 ${t('auto.direct.requirement.no.more.todo')}`));
|
|
488
|
-
// Add a new requirement to the TODO section
|
|
489
|
-
const newRequirement = '### R14: TESTREQ1';
|
|
490
|
-
lines.push(newRequirement);
|
|
491
|
-
}
|
|
492
512
|
|
|
513
|
+
// If no more TODO requirements, log message
|
|
514
|
+
if (!hasMoreTodoRequirements) {
|
|
515
|
+
console.log(chalk.green(`🎉 ${t('auto.direct.requirement.no.more.todo')}`));
|
|
516
|
+
// Add a new requirement to the TODO section
|
|
517
|
+
const newRequirement = '### R14: TESTREQ1';
|
|
518
|
+
lines.push(newRequirement);
|
|
519
|
+
}
|
|
493
520
|
// Find or create TO VERIFY BY HUMAN section with conflict resolution
|
|
494
521
|
// IMPORTANT: Do NOT match VERIFIED sections - only match TO VERIFY sections
|
|
495
522
|
const verifySectionVariants = [
|
|
@@ -499,7 +526,8 @@ const verifySectionVariants = [
|
|
|
499
526
|
'## ✅ TO VERIFY',
|
|
500
527
|
'## ✅ Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG',
|
|
501
528
|
'## 📊 CROSS-COMPUTER REQUIREMENT ASSIGNMENT',
|
|
502
|
-
'## 🖥️ COMPUTER FOCUS AREA MANAGEMENT'
|
|
529
|
+
'## 🖥️ COMPUTER FOCUS AREA MANAGEMENT',
|
|
530
|
+
'## 🔍 spec-kit: TO VERIFY BY HUMAN'
|
|
503
531
|
];
|
|
504
532
|
|
|
505
533
|
let verifyIndex = -1;
|
|
@@ -514,7 +542,7 @@ for (let i = 0; i < lines.length; i++) {
|
|
|
514
542
|
if (trimmed === variantTrimmed || trimmed.startsWith(variantTrimmed)) {
|
|
515
543
|
// Double-check: make sure it's NOT a VERIFIED section (without TO VERIFY)
|
|
516
544
|
if (!trimmed.includes('## 📝 VERIFIED') && !trimmed.match(/^##\s+VERIFIED$/i) &&
|
|
517
|
-
(trimmed.includes('TO VERIFY') || trimmed.includes('Verified by AI screenshot') || trimmed.includes('CROSS-COMPUTER REQUIREMENT ASSIGNMENT') || trimmed.includes('COMPUTER FOCUS AREA MANAGEMENT'))) {
|
|
545
|
+
(trimmed.includes('TO VERIFY') || trimmed.includes('Verified by AI screenshot') || trimmed.includes('CROSS-COMPUTER REQUIREMENT ASSIGNMENT') || trimmed.includes('COMPUTER FOCUS AREA MANAGEMENT') || trimmed.includes('spec-kit'))) {
|
|
518
546
|
verifyIndex = i;
|
|
519
547
|
break;
|
|
520
548
|
}
|
|
@@ -547,13 +575,13 @@ if (verifyIndex === -1) {
|
|
|
547
575
|
if (insertionIndex > 0 && lines[insertionIndex - 1].trim() !== '') {
|
|
548
576
|
block.push('');
|
|
549
577
|
}
|
|
550
|
-
block.push('## 🔍 TO VERIFY BY HUMAN', '### Automatic Registration and Tracking', '### User Registration', '');
|
|
578
|
+
block.push('## 🔍 spec-kit: TO VERIFY BY HUMAN', '### Automatic Registration and Tracking', '### User Registration', '');
|
|
551
579
|
block.push('## 📊 CROSS-COMPUTER REQUIREMENT ASSIGNMENT');
|
|
552
580
|
block.push('## 🖥️ COMPUTER FOCUS AREA MANAGEMENT');
|
|
553
581
|
lines.splice(insertionIndex, 0, ...block);
|
|
554
582
|
verifyIndex = lines.findIndex(line => {
|
|
555
583
|
const trimmed = line.trim();
|
|
556
|
-
return trimmed === '## 🔍 TO VERIFY BY HUMAN' || trimmed.startsWith('## 🔍 TO VERIFY BY HUMAN');
|
|
584
|
+
return trimmed === '## 🔍 spec-kit: TO VERIFY BY HUMAN' || trimmed.startsWith('## 🔍 spec-kit: TO VERIFY BY HUMAN');
|
|
557
585
|
});
|
|
558
586
|
|
|
559
587
|
// Safety check: verifyIndex should be valid
|
|
@@ -616,7 +644,6 @@ for (let i = verifyIndex + 1; i < nextSectionIndex; i++) {
|
|
|
616
644
|
}
|
|
617
645
|
|
|
618
646
|
// Insert requirement block at TOP of TO VERIFY section (right after section header)
|
|
619
|
-
// Always insert immediately after the section header with proper blank line spacing
|
|
620
647
|
let insertIndex = verifyIndex + 1;
|
|
621
648
|
|
|
622
649
|
// Ensure there's a blank line after the section header
|
|
@@ -625,18 +652,77 @@ if (lines[insertIndex]?.trim() !== '') {
|
|
|
625
652
|
insertIndex++;
|
|
626
653
|
}
|
|
627
654
|
|
|
628
|
-
//
|
|
629
|
-
const
|
|
630
|
-
lines.
|
|
631
|
-
|
|
655
|
+
// If a Conflict Resolution header already exists immediately after the section header, reuse it
|
|
656
|
+
const conflictHeader = '### Conflict Resolution:';
|
|
657
|
+
if (lines[insertIndex]?.trim().startsWith(conflictHeader)) {
|
|
658
|
+
// Move insertion point to after the existing header
|
|
659
|
+
insertIndex++;
|
|
660
|
+
// Ensure there's a blank line after the header before inserting the requirement
|
|
661
|
+
if (lines[insertIndex]?.trim() !== '') {
|
|
662
|
+
lines.splice(insertIndex, 0, '');
|
|
663
|
+
insertIndex++;
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
// Insert the conflict header
|
|
667
|
+
lines.splice(insertIndex, 0, conflictHeader);
|
|
668
|
+
insertIndex++;
|
|
669
|
+
// Ensure a blank line after the header
|
|
670
|
+
if (lines[insertIndex]?.trim() !== '') {
|
|
671
|
+
lines.splice(insertIndex, 0, '');
|
|
672
|
+
insertIndex++;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Insert the requirement block
|
|
632
677
|
lines.splice(insertIndex, 0, ...requirementBlock);
|
|
633
678
|
|
|
634
679
|
// Ensure there's a blank line after the requirement block
|
|
635
|
-
const afterIndex = insertIndex + requirementBlock.length
|
|
680
|
+
const afterIndex = insertIndex + requirementBlock.length;
|
|
636
681
|
if (afterIndex < lines.length && lines[afterIndex]?.trim() !== '') {
|
|
637
682
|
lines.splice(afterIndex, 0, '');
|
|
638
683
|
}
|
|
639
684
|
|
|
685
|
+
// Move the requirement to the VERIFIED section
|
|
686
|
+
const verifiedSectionVariants = [
|
|
687
|
+
'## 📝 VERIFIED',
|
|
688
|
+
'## VERIFIED'
|
|
689
|
+
];
|
|
690
|
+
let verifiedIndex = -1;
|
|
691
|
+
for (let i = 0; i < lines.length; i++) {
|
|
692
|
+
const line = lines[i];
|
|
693
|
+
const trimmed = line.trim();
|
|
694
|
+
|
|
695
|
+
// Check each variant more carefully
|
|
696
|
+
for (const variant of verifiedSectionVariants) {
|
|
697
|
+
const variantTrimmed = variant.trim();
|
|
698
|
+
// Exact match or line starts with variant
|
|
699
|
+
if (trimmed === variantTrimmed || trimmed.startsWith(variantTrimmed)) {
|
|
700
|
+
verifiedIndex = i;
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (verifiedIndex !== -1) break;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (verifiedIndex === -1) {
|
|
708
|
+
// Create VERIFIED section - place it after TO VERIFY section
|
|
709
|
+
const block = [];
|
|
710
|
+
block.push('## 📝 VERIFIED');
|
|
711
|
+
lines.splice(verifyIndex + 1, 0, ...block);
|
|
712
|
+
verifiedIndex = lines.findIndex(line => {
|
|
713
|
+
const trimmed = line.trim();
|
|
714
|
+
return trimmed === '## 📝 VERIFIED' || trimmed.startsWith('## 📝 VERIFIED');
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Insert the requirement block at the end of the VERIFIED section
|
|
719
|
+
let insertIndexVerified = verifiedIndex + 1;
|
|
720
|
+
while (insertIndexVerified < lines.length && lines[insertIndexVerified].trim().startsWith('###')) {
|
|
721
|
+
insertIndexVerified++;
|
|
722
|
+
}
|
|
723
|
+
lines.splice(insertIndexVerified, 0, ...requirementBlock);
|
|
724
|
+
|
|
725
|
+
|
|
640
726
|
|
|
641
727
|
// Write the file
|
|
642
728
|
await fs.writeFile(reqPath, lines.join('\n'));
|
|
@@ -792,8 +878,31 @@ async function getAllAvailableProviders() {
|
|
|
792
878
|
case 'windsurf':
|
|
793
879
|
case 'vscode':
|
|
794
880
|
case 'kiro':
|
|
795
|
-
case 'antigravity':
|
|
796
|
-
|
|
881
|
+
case 'antigravity':
|
|
882
|
+
case 'github-copilot':
|
|
883
|
+
case 'amazon-q':
|
|
884
|
+
case 'replit': {
|
|
885
|
+
console.log(chalk.gray(`[DEBUG] Processing provider: ${providerId}, extension: ${def.extension}`));
|
|
886
|
+
if (Array.isArray(def.subAgents) && def.subAgents.length > 0) {
|
|
887
|
+
for (const sub of def.subAgents) {
|
|
888
|
+
providers.push({
|
|
889
|
+
...base,
|
|
890
|
+
model: sub.model,
|
|
891
|
+
subAgentId: sub.id,
|
|
892
|
+
subAgentName: sub.name,
|
|
893
|
+
displayName: `${def.name} (${sub.name})`,
|
|
894
|
+
extension: def.extension
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
} else {
|
|
898
|
+
const providerObj = {
|
|
899
|
+
...base,
|
|
900
|
+
model: def.defaultModel || providerId,
|
|
901
|
+
extension: def.extension
|
|
902
|
+
};
|
|
903
|
+
console.log(chalk.gray(`[DEBUG] Created provider object for ${providerId}:`, JSON.stringify(providerObj, null, 2)));
|
|
904
|
+
providers.push(providerObj);
|
|
905
|
+
}
|
|
797
906
|
break;
|
|
798
907
|
}
|
|
799
908
|
case 'ollama': {
|
|
@@ -829,7 +938,32 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
829
938
|
const providerManager = sharedProviderManager; // Use shared instance to persist rate limit state
|
|
830
939
|
const providers = await getAllAvailableProviders();
|
|
831
940
|
const prefs = await getProviderPreferences();
|
|
832
|
-
|
|
941
|
+
|
|
942
|
+
// Clear any incorrect rate limits for web-based IDEs that were marked due to platform issues
|
|
943
|
+
for (const provider of providers) {
|
|
944
|
+
if (provider.provider === 'replit' && providerManager.isRateLimited('replit', 'replit')) {
|
|
945
|
+
const rateLimitInfo = providerManager.rateLimits['replit:replit'];
|
|
946
|
+
if (rateLimitInfo) {
|
|
947
|
+
const now = Date.now();
|
|
948
|
+
const timeSinceMarked = now - (rateLimitInfo.markedAt || 0);
|
|
949
|
+
const minutesSinceMarked = timeSinceMarked / (1000 * 60);
|
|
950
|
+
|
|
951
|
+
// Clear rate limit if:
|
|
952
|
+
// 1. It was marked due to platform issues, OR
|
|
953
|
+
// 2. It was marked within the last 5 minutes (likely a recent platform issue)
|
|
954
|
+
const isPlatformError = rateLimitInfo.reason &&
|
|
955
|
+
(rateLimitInfo.reason.includes('xdg-open') ||
|
|
956
|
+
rateLimitInfo.reason.includes('command not found') ||
|
|
957
|
+
rateLimitInfo.reason.includes('Unable to find application'));
|
|
958
|
+
|
|
959
|
+
if (isPlatformError || minutesSinceMarked < 5) {
|
|
960
|
+
console.log(chalk.yellow(`⚠️ Clearing incorrect rate limit for Replit (marked ${minutesSinceMarked.toFixed(1)} minutes ago: ${isPlatformError ? 'platform error' : 'recent error'})`));
|
|
961
|
+
delete providerManager.rateLimits['replit:replit'];
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
833
967
|
// Get first ENABLED agent from provider preferences (same logic as interactive menu)
|
|
834
968
|
let firstEnabledAgent = null;
|
|
835
969
|
for (const agentId of prefs.order) {
|
|
@@ -839,6 +973,13 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
839
973
|
}
|
|
840
974
|
}
|
|
841
975
|
const savedAgent = firstEnabledAgent || config.agent || config.ide;
|
|
976
|
+
|
|
977
|
+
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
978
|
+
console.log(chalk.gray(`[DEBUG] firstEnabledAgent: ${firstEnabledAgent}`));
|
|
979
|
+
console.log(chalk.gray(`[DEBUG] config.agent: ${config.agent}`));
|
|
980
|
+
console.log(chalk.gray(`[DEBUG] config.ide: ${config.ide}`));
|
|
981
|
+
console.log(chalk.gray(`[DEBUG] savedAgent: ${savedAgent}`));
|
|
982
|
+
}
|
|
842
983
|
|
|
843
984
|
if (providers.length === 0) {
|
|
844
985
|
return { status: 'no_providers', providers: [] };
|
|
@@ -854,17 +995,147 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
854
995
|
const availableProviders = enabledProviders.filter(p => {
|
|
855
996
|
// Exclude the specified provider and rate-limited providers
|
|
856
997
|
if (excludeProvider && p.provider === excludeProvider) {
|
|
998
|
+
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
999
|
+
console.log(chalk.gray(`[DEBUG] Excluding ${p.provider} due to excludeProvider=${excludeProvider}`));
|
|
1000
|
+
}
|
|
857
1001
|
return false;
|
|
858
1002
|
}
|
|
859
|
-
|
|
1003
|
+
|
|
1004
|
+
// For IDE providers, only consider them rate limited if they've actually been used
|
|
1005
|
+
// IDE providers that haven't been used yet should always be available for launch/installation
|
|
1006
|
+
if (p.type === 'ide') {
|
|
1007
|
+
// Check if this IDE provider has been used before by looking for any rate limit entries
|
|
1008
|
+
const hasBeenUsed = providerManager.rateLimits[`${p.provider}:`] ||
|
|
1009
|
+
Object.keys(providerManager.rateLimits).some(key => key.startsWith(`${p.provider}:`));
|
|
1010
|
+
|
|
1011
|
+
// Debug: Log IDE provider status
|
|
1012
|
+
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1013
|
+
console.log(chalk.gray(`[DEBUG] IDE Provider ${p.provider}: hasBeenUsed=${hasBeenUsed}, isRateLimited=${providerManager.isRateLimited(p.provider, p.model)}`));
|
|
1014
|
+
console.log(chalk.gray(`[DEBUG] Rate limits for ${p.provider}:`, JSON.stringify(providerManager.rateLimits, null, 2)));
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// If it has been used, check if it's currently rate limited
|
|
1018
|
+
if (hasBeenUsed) {
|
|
1019
|
+
const isRateLimited = providerManager.isRateLimited(p.provider, p.model);
|
|
1020
|
+
if (process.env.DEBUG_PROVIDER_SELECTION && isRateLimited) {
|
|
1021
|
+
console.log(chalk.gray(`[DEBUG] ${p.provider} is rate limited, excluding`));
|
|
1022
|
+
}
|
|
1023
|
+
return !isRateLimited;
|
|
1024
|
+
}
|
|
1025
|
+
// If it hasn't been used yet, it's always available
|
|
1026
|
+
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1027
|
+
console.log(chalk.gray(`[DEBUG] ${p.provider} hasn't been used yet, including`));
|
|
1028
|
+
}
|
|
1029
|
+
return true;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// For non-IDE providers, check rate limits normally
|
|
1033
|
+
const isRateLimited = providerManager.isRateLimited(p.provider, p.model);
|
|
1034
|
+
if (process.env.DEBUG_PROVIDER_SELECTION && isRateLimited) {
|
|
1035
|
+
console.log(chalk.gray(`[DEBUG] Non-IDE provider ${p.provider} is rate limited, excluding`));
|
|
1036
|
+
}
|
|
1037
|
+
return !isRateLimited;
|
|
860
1038
|
});
|
|
861
1039
|
|
|
1040
|
+
// If no providers are available, try to enable some IDE providers that haven't been used yet
|
|
1041
|
+
if (availableProviders.length === 0) {
|
|
1042
|
+
const allProviders = await getAllAvailableProviders();
|
|
1043
|
+
const unusedIdeProviders = allProviders.filter(p =>
|
|
1044
|
+
p.type === 'ide' &&
|
|
1045
|
+
!enabledProviders.some(ep => ep.provider === p.provider) &&
|
|
1046
|
+
!providerManager.rateLimits[`${p.provider}:`] &&
|
|
1047
|
+
!Object.keys(providerManager.rateLimits).some(key => key.startsWith(`${p.provider}:`))
|
|
1048
|
+
);
|
|
1049
|
+
|
|
1050
|
+
if (unusedIdeProviders.length > 0) {
|
|
1051
|
+
console.log(chalk.yellow(`⚠️ No enabled providers available. Auto-enabling unused IDE providers...`));
|
|
1052
|
+
const prefs = await getProviderPreferences();
|
|
1053
|
+
|
|
1054
|
+
// Enable the first few unused IDE providers
|
|
1055
|
+
const providersToEnable = unusedIdeProviders.slice(0, 3);
|
|
1056
|
+
for (const provider of providersToEnable) {
|
|
1057
|
+
prefs.enabled[provider.provider] = true;
|
|
1058
|
+
console.log(chalk.gray(` ✓ Enabled ${provider.displayName}`));
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
await saveProviderPreferences(prefs.order, prefs.enabled);
|
|
1062
|
+
|
|
1063
|
+
// Recalculate available providers with the newly enabled ones
|
|
1064
|
+
const newlyEnabledProviders = allProviders.filter(p =>
|
|
1065
|
+
prefs.enabled[p.provider] !== false
|
|
1066
|
+
);
|
|
1067
|
+
|
|
1068
|
+
const newlyAvailableProviders = newlyEnabledProviders.filter(p => {
|
|
1069
|
+
// Exclude the specified provider
|
|
1070
|
+
if (excludeProvider && p.provider === excludeProvider) {
|
|
1071
|
+
return false;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// For newly enabled IDE providers, they haven't been used yet, so they're always available
|
|
1075
|
+
if (p.type === 'ide') {
|
|
1076
|
+
return true;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// For non-IDE providers, check rate limits normally
|
|
1080
|
+
return !providerManager.isRateLimited(p.provider, p.model);
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
// Add the newly available providers to the list
|
|
1084
|
+
availableProviders.push(...newlyAvailableProviders);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Debug: Log provider selection details
|
|
1089
|
+
if (process.env.DEBUG_PROVIDER_SELECTION || true) { // Force debug for now
|
|
1090
|
+
console.log(chalk.gray(`[DEBUG] Total providers: ${providers.length}`));
|
|
1091
|
+
console.log(chalk.gray(`[DEBUG] Enabled providers: ${enabledProviders.length}`));
|
|
1092
|
+
console.log(chalk.gray(`[DEBUG] Available providers: ${availableProviders.length}`));
|
|
1093
|
+
console.log(chalk.gray(`[DEBUG] Available provider names: ${availableProviders.map(p => p.provider).join(', ')}`));
|
|
1094
|
+
console.log(chalk.gray(`[DEBUG] Enabled provider names: ${enabledProviders.map(p => p.provider).join(', ')}`));
|
|
1095
|
+
console.log(chalk.gray(`[DEBUG] Excluding provider: ${excludeProvider}`));
|
|
1096
|
+
console.log(chalk.gray(`[DEBUG] Looking for savedAgent: ${savedAgent}`));
|
|
1097
|
+
}
|
|
1098
|
+
|
|
862
1099
|
let selection = null;
|
|
863
1100
|
if (savedAgent && savedAgent !== excludeProvider) {
|
|
864
1101
|
selection = availableProviders.find(p => p.provider === savedAgent);
|
|
1102
|
+
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1103
|
+
console.log(chalk.gray(`[DEBUG] Looking for savedAgent: ${savedAgent}`));
|
|
1104
|
+
console.log(chalk.gray(`[DEBUG] Found selection: ${selection ? selection.provider : 'null'}`));
|
|
1105
|
+
}
|
|
865
1106
|
}
|
|
866
|
-
|
|
867
|
-
|
|
1107
|
+
|
|
1108
|
+
// If no selection or the selected provider is rate limited, try to find an unused IDE provider
|
|
1109
|
+
if (!selection || (selection.type === 'ide' && providerManager.isRateLimited(selection.provider, selection.model))) {
|
|
1110
|
+
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1111
|
+
console.log(chalk.gray(`[DEBUG] No valid selection, trying unused IDE providers`));
|
|
1112
|
+
console.log(chalk.gray(`[DEBUG] Reason: ${!selection ? 'no selection' : 'selection is rate limited'}`));
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Prioritize newly enabled IDE providers that haven't been used
|
|
1116
|
+
const unusedIdeProviders = availableProviders.filter(p =>
|
|
1117
|
+
p.type === 'ide' &&
|
|
1118
|
+
!providerManager.rateLimits[`${p.provider}:`] &&
|
|
1119
|
+
!Object.keys(providerManager.rateLimits).some(key => key.startsWith(`${p.provider}:`))
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1122
|
+
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1123
|
+
console.log(chalk.gray(`[DEBUG] Unused IDE providers: ${unusedIdeProviders.map(p => p.provider).join(', ')}`));
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
if (unusedIdeProviders.length > 0) {
|
|
1127
|
+
selection = unusedIdeProviders[0];
|
|
1128
|
+
console.log(chalk.green(`✓ Selected unused IDE provider: ${selection.displayName}`));
|
|
1129
|
+
} else if (availableProviders.length > 0) {
|
|
1130
|
+
selection = availableProviders[0];
|
|
1131
|
+
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1132
|
+
console.log(chalk.gray(`[DEBUG] Selected first available provider: ${selection.provider}`));
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
} else if (selection) {
|
|
1136
|
+
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1137
|
+
console.log(chalk.gray(`[DEBUG] Using savedAgent selection: ${selection.provider}`));
|
|
1138
|
+
}
|
|
868
1139
|
}
|
|
869
1140
|
|
|
870
1141
|
if (selection) {
|
|
@@ -888,10 +1159,16 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
888
1159
|
};
|
|
889
1160
|
}
|
|
890
1161
|
|
|
891
|
-
async function acquireProviderConfig(excludeProvider = null) {
|
|
1162
|
+
async function acquireProviderConfig(excludeProvider = null, excludeModel = null) {
|
|
892
1163
|
while (true) {
|
|
893
1164
|
const selection = await getProviderConfig(excludeProvider);
|
|
894
1165
|
if (selection.status === 'ok') {
|
|
1166
|
+
// If we have a specific model to exclude (for same-IDE failover), skip it
|
|
1167
|
+
if (excludeModel && selection.provider.model === excludeModel) {
|
|
1168
|
+
console.log(chalk.yellow(`⚠️ Excluding rate-limited sub-agent: ${selection.provider.displayName}\n`));
|
|
1169
|
+
// Retry with the same provider excluded to force picking another sub-agent
|
|
1170
|
+
return acquireProviderConfig(selection.provider.provider, selection.provider.model);
|
|
1171
|
+
}
|
|
895
1172
|
return selection.provider;
|
|
896
1173
|
}
|
|
897
1174
|
|
|
@@ -1338,6 +1615,13 @@ async function runIdeProviderIteration(providerConfig, repoPath) {
|
|
|
1338
1615
|
console.log(chalk.cyan(`⚙️ ${t('auto.direct.provider.launching', { provider: providerConfig.displayName })}\n`));
|
|
1339
1616
|
|
|
1340
1617
|
const args = [CLI_ENTRY_POINT, 'auto:start', '--ide', providerConfig.ide || providerConfig.provider, '--max-chats', String(providerConfig.maxChats || 1)];
|
|
1618
|
+
if (providerConfig.model) {
|
|
1619
|
+
args.push('--ide-model', String(providerConfig.model));
|
|
1620
|
+
}
|
|
1621
|
+
// Pass extension information for VS Code extensions
|
|
1622
|
+
if (providerConfig.extension) {
|
|
1623
|
+
args.push('--extension', String(providerConfig.extension));
|
|
1624
|
+
}
|
|
1341
1625
|
const child = spawn(process.execPath, args, {
|
|
1342
1626
|
cwd: repoPath,
|
|
1343
1627
|
env: process.env,
|
|
@@ -1372,13 +1656,15 @@ async function runIdeProviderIteration(providerConfig, repoPath) {
|
|
|
1372
1656
|
} else {
|
|
1373
1657
|
const message = `${providerConfig.displayName} exited with code ${code}`;
|
|
1374
1658
|
const antigravityRateLimit = checkAntigravityRateLimit(combinedOutput);
|
|
1659
|
+
const kiroRateLimit = checkKiroRateLimit(combinedOutput);
|
|
1375
1660
|
|
|
1376
1661
|
resolve({
|
|
1377
1662
|
success: false,
|
|
1378
1663
|
error: combinedOutput ? `${message}\n${combinedOutput}` : message,
|
|
1379
1664
|
output: combinedOutput,
|
|
1380
|
-
rateLimited: isRateLimitMessage(combinedOutput) || antigravityRateLimit.isRateLimited,
|
|
1381
|
-
antigravityRateLimited: antigravityRateLimit.isRateLimited
|
|
1665
|
+
rateLimited: isRateLimitMessage(combinedOutput) || antigravityRateLimit.isRateLimited || kiroRateLimit.isRateLimited,
|
|
1666
|
+
antigravityRateLimited: antigravityRateLimit.isRateLimited,
|
|
1667
|
+
kiroRateLimited: kiroRateLimit.isRateLimited
|
|
1382
1668
|
});
|
|
1383
1669
|
}
|
|
1384
1670
|
});
|
|
@@ -1421,7 +1707,7 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
1421
1707
|
let foundInVerified = false;
|
|
1422
1708
|
|
|
1423
1709
|
for (const line of lines) {
|
|
1424
|
-
if (line.includes('## ✅ Verified by AI
|
|
1710
|
+
if (line.includes('## ✅ Verified by AI')) {
|
|
1425
1711
|
inVerifiedSection = true;
|
|
1426
1712
|
continue;
|
|
1427
1713
|
}
|
|
@@ -1451,6 +1737,10 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
1451
1737
|
for (const line of lines) {
|
|
1452
1738
|
if (line.includes('🚦 Current Status')) {
|
|
1453
1739
|
inStatusSection = true;
|
|
1740
|
+
if (line.includes('DONE')) {
|
|
1741
|
+
statusContainsDone = true;
|
|
1742
|
+
break;
|
|
1743
|
+
}
|
|
1454
1744
|
continue;
|
|
1455
1745
|
}
|
|
1456
1746
|
|
|
@@ -1472,21 +1762,72 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
1472
1762
|
return;
|
|
1473
1763
|
}
|
|
1474
1764
|
|
|
1475
|
-
// Check 3:
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1765
|
+
// Check 3: Detect rate-limit messages written into the REQUIREMENTS file
|
|
1766
|
+
// Examples:
|
|
1767
|
+
// - "You have reached the quota limit for this model. You can resume using this model at 1/19/2026, 4:07:27 PM."
|
|
1768
|
+
// - "Please try again in 15m5.472s"
|
|
1769
|
+
// - "Spending cap reached resets Jan 17 at 12pm"
|
|
1770
|
+
// - "Usage cap reached. Try again in 15 minutes."
|
|
1771
|
+
// - "You've reached your monthly chat messages quota" (GitHub Copilot)
|
|
1772
|
+
// - "Upgrade to Copilot Pro" (GitHub Copilot)
|
|
1773
|
+
// - "wait for your allowance to renew" (GitHub Copilot)
|
|
1774
|
+
const rateLimitPattern = /(quota limit|you have reached( the)? quota|you can resume using this model at|please try again in|try again in|spending cap reached|usage cap( reached)?|you\'ve hit( the)? usage limit|you\u2019ve hit( the)? usage limit|cap reached|limit exceeded|exceeded (quota|limit)|monthly.*quota|upgrade to.*pro|allowance to renew|chat messages quota)/i;
|
|
1775
|
+
// Avoid matching requirement headings or bullets (these may mention "rate limit" as part of the requirement text)
|
|
1776
|
+
const matchedLine = lines.find(l => rateLimitPattern.test(l) && !l.trim().startsWith('###') && !l.trim().startsWith('-'));
|
|
1777
|
+
if (matchedLine) {
|
|
1778
|
+
// Mark the provider as rate limited (if we can) and signal a rate-limited completion
|
|
1779
|
+
try {
|
|
1780
|
+
if (ideType && sharedProviderManager && typeof sharedProviderManager.markRateLimited === 'function') {
|
|
1781
|
+
sharedProviderManager.markRateLimited(ideType, undefined, matchedLine);
|
|
1782
|
+
}
|
|
1783
|
+
} catch (e) {
|
|
1784
|
+
// Ignore errors from marking rate-limited
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1480
1787
|
watcher.close();
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
antigravityRateLimited: true // This triggers switching to next provider
|
|
1485
|
-
});
|
|
1788
|
+
console.log(chalk.yellow(`\n⚠️ Rate limit message detected from IDE: ${matchedLine}\n`));
|
|
1789
|
+
// Return a generic rateLimited flag and include the matched line for diagnostics
|
|
1790
|
+
resolve({ success: false, rateLimited: true, providerRateLimited: ideType || undefined, matchedLine });
|
|
1486
1791
|
return;
|
|
1487
1792
|
}
|
|
1488
1793
|
|
|
1489
|
-
// Check 4:
|
|
1794
|
+
// Check 4: Active quota detection via CDP (for VS Code, Cursor, github-copilot, amazon-q)
|
|
1795
|
+
// This actively checks the IDE UI for quota warnings, not just the REQUIREMENTS file
|
|
1796
|
+
if (!quotaHandled && ideType && (ideType === 'vscode' || ideType === 'cursor' || ideType === 'github-copilot' || ideType === 'amazon-q')) {
|
|
1797
|
+
try {
|
|
1798
|
+
const quotaDetector = new QuotaDetector();
|
|
1799
|
+
const ideToCheck = ideType === 'github-copilot' || ideType === 'amazon-q' ? 'vscode' : ideType;
|
|
1800
|
+
const quotaResult = await quotaDetector.detectQuotaWarning(ideToCheck);
|
|
1801
|
+
|
|
1802
|
+
if (quotaResult && quotaResult.hasQuotaWarning) {
|
|
1803
|
+
quotaHandled = true;
|
|
1804
|
+
const quotaMessage = quotaResult.matchedText || 'Quota limit detected in IDE UI';
|
|
1805
|
+
|
|
1806
|
+
// Mark the provider as rate limited
|
|
1807
|
+
try {
|
|
1808
|
+
if (sharedProviderManager && typeof sharedProviderManager.markRateLimited === 'function') {
|
|
1809
|
+
sharedProviderManager.markRateLimited(ideType, undefined, quotaMessage);
|
|
1810
|
+
}
|
|
1811
|
+
} catch (e) {
|
|
1812
|
+
// Ignore errors from marking rate-limited
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
watcher.close();
|
|
1816
|
+
console.log(chalk.yellow(`\n⚠️ Quota warning detected in ${ideToCheck} UI: ${quotaMessage.substring(0, 100)}...\n`));
|
|
1817
|
+
resolve({ success: false, rateLimited: true, providerRateLimited: ideType, matchedLine: quotaMessage });
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
} catch (quotaError) {
|
|
1821
|
+
// Quota detection failed - this is non-fatal, continue waiting
|
|
1822
|
+
// Only log errors every 30 seconds to avoid spam
|
|
1823
|
+
if (Date.now() - lastCheckTime >= 30000) {
|
|
1824
|
+
console.log(chalk.gray(` (Quota detection check failed: ${quotaError.message})`));
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
// Check 5: Timeout
|
|
1830
|
+
const elapsed = Date.now() - startTime;
|
|
1490
1831
|
if (elapsed >= timeoutMs) {
|
|
1491
1832
|
watcher.close();
|
|
1492
1833
|
console.log(chalk.yellow(`\n⚠️ Timeout after ${Math.floor(elapsed / 60000)} minutes\n`));
|
|
@@ -1541,30 +1882,110 @@ async function runIdeFallbackIteration(requirement, providerConfig, repoPath, pr
|
|
|
1541
1882
|
|
|
1542
1883
|
if (!ideResult.success) {
|
|
1543
1884
|
if (ideResult.antigravityRateLimited) {
|
|
1544
|
-
|
|
1545
|
-
|
|
1885
|
+
providerManager.markRateLimited(providerConfig.provider, providerConfig.model, 'Quota limit reached');
|
|
1886
|
+
const switchResult = await handleAntigravityRateLimit();
|
|
1887
|
+
if (switchResult && switchResult.modelSwitched) {
|
|
1888
|
+
return { success: false, error: `Antigravity switched to ${switchResult.nextModel}, retrying.`, shouldRetry: true };
|
|
1889
|
+
}
|
|
1890
|
+
return { success: false, error: 'Antigravity rate limit reached, retrying with next provider.', shouldRetry: true };
|
|
1546
1891
|
}
|
|
1892
|
+
|
|
1547
1893
|
// CRITICAL: Mark provider as unavailable for ANY error so acquireProviderConfig() will skip it
|
|
1548
|
-
|
|
1894
|
+
// EXCEPT for web-based IDEs where the error is platform/browser related
|
|
1895
|
+
const error = ideResult.output || ideResult.error || 'IDE provider failed';
|
|
1896
|
+
const isWebBasedIDE = providerConfig.provider === 'replit';
|
|
1897
|
+
const isPlatformError = error.includes('xdg-open') || error.includes('command not found') || error.includes('Unable to find application');
|
|
1898
|
+
|
|
1899
|
+
if (!isWebBasedIDE || !isPlatformError) {
|
|
1900
|
+
providerManager.markRateLimited(providerConfig.provider, providerConfig.model, error);
|
|
1901
|
+
} else {
|
|
1902
|
+
// For web-based IDEs with platform errors, don't mark as rate limited
|
|
1903
|
+
// Just log the error and let the system try the next provider
|
|
1904
|
+
console.log(chalk.yellow(`⚠️ Web-based IDE ${providerConfig.provider} failed due to platform issue: ${error}`));
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1549
1907
|
return { success: false, error: ideResult.error || 'IDE provider failed' };
|
|
1550
1908
|
}
|
|
1551
1909
|
|
|
1552
1910
|
console.log(chalk.green(`✓ ${t('auto.direct.ide.prompt.sent')}`));
|
|
1553
1911
|
|
|
1554
1912
|
// Wait for IDE agent to complete the work (IDE will update status to DONE itself)
|
|
1913
|
+
const ideCompletionStartTime = Date.now();
|
|
1555
1914
|
const completionResult = await waitForIdeCompletion(repoPath, requirement.text, providerConfig.ide || providerConfig.provider);
|
|
1915
|
+
const ideResponseTime = Date.now() - ideCompletionStartTime;
|
|
1916
|
+
|
|
1917
|
+
// Track IDE health metrics based on completion result
|
|
1918
|
+
const ideType = providerConfig.ide || providerConfig.provider;
|
|
1919
|
+
if (completionResult.success) {
|
|
1920
|
+
// Record success with response time
|
|
1921
|
+
await sharedHealthTracker.recordSuccess(ideType, ideResponseTime, {
|
|
1922
|
+
requirementId: requirement.id || requirement.text.substring(0, 50),
|
|
1923
|
+
continuationPromptsDetected: 0, // Will be updated when continuation detection is implemented
|
|
1924
|
+
});
|
|
1925
|
+
} else if (completionResult.rateLimited) {
|
|
1926
|
+
// Record quota event (does NOT increment success/failure counters per FR-008)
|
|
1927
|
+
await sharedHealthTracker.recordQuota(ideType, completionResult.matchedLine || 'Quota limit detected', {
|
|
1928
|
+
requirementId: requirement.id || requirement.text.substring(0, 50),
|
|
1929
|
+
});
|
|
1930
|
+
} else if (completionResult.reason === 'timeout') {
|
|
1931
|
+
// Record failure due to timeout
|
|
1932
|
+
await sharedHealthTracker.recordFailure(ideType, 'Timeout exceeded', {
|
|
1933
|
+
timeoutUsed: 30 * 60 * 1000, // Default 30 minutes, will be adaptive later
|
|
1934
|
+
requirementId: requirement.id || requirement.text.substring(0, 50),
|
|
1935
|
+
});
|
|
1936
|
+
} else {
|
|
1937
|
+
// Record other failure
|
|
1938
|
+
await sharedHealthTracker.recordFailure(ideType, completionResult.error || 'Unknown error', {
|
|
1939
|
+
requirementId: requirement.id || requirement.text.substring(0, 50),
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1556
1942
|
|
|
1557
1943
|
if (!completionResult.success) {
|
|
1944
|
+
// Special-case behavior for Antigravity CLI installs (they have special handling)
|
|
1558
1945
|
if (completionResult.antigravityRateLimited) {
|
|
1559
1946
|
console.log(chalk.yellow(`⚠️ ${t('auto.direct.provider.quota.exhausted', { provider: 'Antigravity' })}\n`));
|
|
1560
1947
|
providerManager.markRateLimited(providerConfig.provider, providerConfig.model, 'Quota limit reached');
|
|
1948
|
+
|
|
1949
|
+
const switchResult = await handleAntigravityRateLimit();
|
|
1950
|
+
if (switchResult && switchResult.modelSwitched) {
|
|
1951
|
+
return { success: false, error: `Antigravity switched to ${switchResult.nextModel}, retrying.`, shouldRetry: true };
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1561
1954
|
return { success: false, error: 'Antigravity quota limit', shouldRetry: true };
|
|
1562
1955
|
}
|
|
1563
1956
|
|
|
1957
|
+
// Special-case behavior for Kiro IDE (they have special handling)
|
|
1958
|
+
if (completionResult.kiroRateLimited) {
|
|
1959
|
+
console.log(chalk.yellow(`⚠️ ${t('auto.direct.provider.quota.exhausted', { provider: 'AWS Kiro' })}\n`));
|
|
1960
|
+
providerManager.markRateLimited(providerConfig.provider, providerConfig.model, 'Quota limit reached');
|
|
1961
|
+
|
|
1962
|
+
const switchResult = await handleKiroRateLimit();
|
|
1963
|
+
if (switchResult && switchResult.success) {
|
|
1964
|
+
return { success: false, error: `AWS Kiro switched to ${switchResult.nextProvider}, retrying.`, shouldRetry: true };
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
return { success: false, error: 'AWS Kiro quota limit', shouldRetry: true };
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// Generic rate-limited behavior: if the completion detected a rate-limited message in REQUIREMENTS,
|
|
1971
|
+
// mark the provider as rate-limited and retry with the next available provider.
|
|
1972
|
+
if (completionResult.rateLimited) {
|
|
1973
|
+
console.log(chalk.yellow(`⚠️ Provider ${providerConfig.provider} reported quota/exhaustion: ${completionResult.matchedLine || ''}\n`));
|
|
1974
|
+
providerManager.markRateLimited(providerConfig.provider, providerConfig.model, completionResult.matchedLine || 'Quota limit detected');
|
|
1975
|
+
return { success: false, error: 'Provider quota limit', shouldRetry: true };
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1564
1978
|
const errorMsg = completionResult.reason === 'timeout'
|
|
1565
1979
|
? 'IDE agent timed out'
|
|
1566
1980
|
: 'IDE agent failed to complete';
|
|
1567
1981
|
providerManager.markRateLimited(providerConfig.provider, providerConfig.model, errorMsg);
|
|
1982
|
+
|
|
1983
|
+
// Automatically retry with next IDE on timeout (T024: automatic IDE switching)
|
|
1984
|
+
if (completionResult.reason === 'timeout') {
|
|
1985
|
+
console.log(chalk.yellow(`⏰ Timeout detected - switching to next available IDE\n`));
|
|
1986
|
+
return { success: false, error: errorMsg, shouldRetry: true };
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1568
1989
|
return { success: false, error: errorMsg };
|
|
1569
1990
|
}
|
|
1570
1991
|
|
|
@@ -1633,7 +2054,10 @@ async function runIteration(requirement, providerConfig, repoPath) {
|
|
|
1633
2054
|
// ═══════════════════════════════════════════════════════════
|
|
1634
2055
|
printStatusCard(requirement.text, 'ACT');
|
|
1635
2056
|
|
|
1636
|
-
console.log(chalk.cyan(
|
|
2057
|
+
console.log(chalk.cyan(` ${getLogTimestamp()} - 🤖 Asking LLM for implementation...\n`));
|
|
2058
|
+
console.log(chalk.gray('─'.repeat(80)));
|
|
2059
|
+
console.log(chalk.yellow('💭 LLM Response (streaming):'));
|
|
2060
|
+
console.log(chalk.gray('─'.repeat(80)));
|
|
1637
2061
|
|
|
1638
2062
|
// Build context with actual file snippets
|
|
1639
2063
|
let contextSection = '';
|
|
@@ -1752,27 +2176,97 @@ if (counts.todoCount === 0) {
|
|
|
1752
2176
|
Now implement the requirement. Remember: COPY THE SEARCH BLOCK EXACTLY!`;
|
|
1753
2177
|
|
|
1754
2178
|
let fullResponse = '';
|
|
2179
|
+
let chunkCount = 0;
|
|
2180
|
+
let totalChars = 0;
|
|
2181
|
+
|
|
2182
|
+
// Show spinner while waiting for first chunk
|
|
2183
|
+
const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
2184
|
+
let spinnerIndex = 0;
|
|
2185
|
+
let spinnerInterval = null;
|
|
2186
|
+
let receivedFirstChunk = false;
|
|
2187
|
+
|
|
2188
|
+
const startSpinner = () => {
|
|
2189
|
+
process.stdout.write(chalk.cyan('⏳ Waiting for response'));
|
|
2190
|
+
spinnerInterval = setInterval(() => {
|
|
2191
|
+
process.stdout.write(`\r${chalk.cyan('⏳ Waiting for response')} ${chalk.yellow(spinnerFrames[spinnerIndex])}`);
|
|
2192
|
+
spinnerIndex = (spinnerIndex + 1) % spinnerFrames.length;
|
|
2193
|
+
}, 100);
|
|
2194
|
+
};
|
|
2195
|
+
|
|
2196
|
+
const stopSpinner = () => {
|
|
2197
|
+
if (spinnerInterval) {
|
|
2198
|
+
clearInterval(spinnerInterval);
|
|
2199
|
+
spinnerInterval = null;
|
|
2200
|
+
process.stdout.write('\r\x1b[K'); // Clear the line
|
|
2201
|
+
}
|
|
2202
|
+
};
|
|
2203
|
+
|
|
2204
|
+
startSpinner();
|
|
1755
2205
|
|
|
1756
2206
|
const result = await llm.call(providerConfig, prompt, {
|
|
1757
2207
|
temperature: 0.1,
|
|
1758
2208
|
maxTokens: 4096,
|
|
1759
2209
|
onChunk: (chunk) => {
|
|
1760
|
-
|
|
2210
|
+
chunkCount++;
|
|
2211
|
+
totalChars += chunk.length;
|
|
2212
|
+
// Show first chunk arrival to confirm streaming started
|
|
2213
|
+
if (chunkCount === 1) {
|
|
2214
|
+
stopSpinner();
|
|
2215
|
+
receivedFirstChunk = true;
|
|
2216
|
+
process.stdout.write(chalk.green('✓ Streaming started...\n'));
|
|
2217
|
+
}
|
|
2218
|
+
// Use white text for better visibility instead of gray
|
|
2219
|
+
process.stdout.write(chalk.white(chunk));
|
|
1761
2220
|
fullResponse += chunk;
|
|
1762
2221
|
},
|
|
1763
2222
|
onComplete: () => {
|
|
1764
|
-
|
|
2223
|
+
stopSpinner();
|
|
2224
|
+
if (chunkCount > 0) {
|
|
2225
|
+
console.log(chalk.green(`\n✓ Received ${totalChars} characters in ${chunkCount} chunks`));
|
|
2226
|
+
} else {
|
|
2227
|
+
console.log(chalk.yellow('\n⚠️ No streaming response received (using complete response)'));
|
|
2228
|
+
}
|
|
2229
|
+
console.log(chalk.gray('─'.repeat(80)));
|
|
2230
|
+
console.log();
|
|
1765
2231
|
},
|
|
1766
2232
|
onError: (error) => {
|
|
2233
|
+
stopSpinner();
|
|
1767
2234
|
console.error(chalk.red(`\n✗ Error: ${error}`));
|
|
1768
2235
|
}
|
|
1769
2236
|
});
|
|
1770
2237
|
|
|
2238
|
+
// Ensure spinner is stopped even if callbacks didn't fire
|
|
2239
|
+
stopSpinner();
|
|
2240
|
+
|
|
1771
2241
|
if (!result.success) {
|
|
1772
2242
|
const combinedError = [result.error, fullResponse].filter(Boolean).join('\n').trim() || 'Unknown error';
|
|
1773
2243
|
console.log(chalk.red(`\n✗ LLM call failed: ${combinedError}`));
|
|
1774
2244
|
// CRITICAL: Mark provider as rate-limited for ANY error so acquireProviderConfig() will skip it
|
|
1775
2245
|
providerManager.markRateLimited(providerConfig.provider, providerConfig.model, combinedError);
|
|
2246
|
+
|
|
2247
|
+
// Track health metrics for failed direct providers
|
|
2248
|
+
const providerType = providerConfig.provider;
|
|
2249
|
+
const duration = Date.now() - startTime;
|
|
2250
|
+
try {
|
|
2251
|
+
// Detect quota/rate limit errors vs other failures
|
|
2252
|
+
const quotaPattern = /(quota|rate.?limit|usage.?limit|spending.?cap|allowance|exceeded|overloaded)/i;
|
|
2253
|
+
const isQuotaError = quotaPattern.test(combinedError);
|
|
2254
|
+
|
|
2255
|
+
if (isQuotaError) {
|
|
2256
|
+
await sharedHealthTracker.recordQuota(providerType, combinedError, {
|
|
2257
|
+
requirementId: requirement.id || requirement.text.substring(0, 50),
|
|
2258
|
+
});
|
|
2259
|
+
} else {
|
|
2260
|
+
await sharedHealthTracker.recordFailure(providerType, combinedError, {
|
|
2261
|
+
timeoutUsed: duration,
|
|
2262
|
+
requirementId: requirement.id || requirement.text.substring(0, 50),
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
} catch (healthError) {
|
|
2266
|
+
// Don't fail the iteration if health tracking fails
|
|
2267
|
+
console.log(chalk.gray(`⚠️ Health tracking error: ${healthError.message}`));
|
|
2268
|
+
}
|
|
2269
|
+
|
|
1776
2270
|
return { success: false, error: combinedError };
|
|
1777
2271
|
}
|
|
1778
2272
|
|
|
@@ -1899,6 +2393,18 @@ Now implement the requirement. Remember: COPY THE SEARCH BLOCK EXACTLY!`;
|
|
|
1899
2393
|
const duration = Date.now() - startTime;
|
|
1900
2394
|
providerManager.recordPerformance(providerConfig.provider, providerConfig.model, duration);
|
|
1901
2395
|
|
|
2396
|
+
// Track health metrics for direct providers (IDE providers tracked in runIdeFallbackIteration)
|
|
2397
|
+
const providerType = providerConfig.provider;
|
|
2398
|
+
try {
|
|
2399
|
+
await sharedHealthTracker.recordSuccess(providerType, duration, {
|
|
2400
|
+
requirementId: requirement.id || requirement.text.substring(0, 50),
|
|
2401
|
+
continuationPromptsDetected: 0,
|
|
2402
|
+
});
|
|
2403
|
+
} catch (healthError) {
|
|
2404
|
+
// Don't fail the iteration if health tracking fails
|
|
2405
|
+
console.log(chalk.gray(`⚠️ Health tracking error: ${healthError.message}`));
|
|
2406
|
+
}
|
|
2407
|
+
|
|
1902
2408
|
console.log();
|
|
1903
2409
|
console.log(chalk.gray('─'.repeat(80)));
|
|
1904
2410
|
console.log();
|
|
@@ -1929,10 +2435,6 @@ async function handleAutoStart(options) {
|
|
|
1929
2435
|
console.log(chalk.gray('═'.repeat(80)));
|
|
1930
2436
|
console.log();
|
|
1931
2437
|
|
|
1932
|
-
// Get repo path
|
|
1933
|
-
// Load configured stages
|
|
1934
|
-
configuredStages = await getStages();
|
|
1935
|
-
|
|
1936
2438
|
const repoPath = await getRepoPath();
|
|
1937
2439
|
if (!repoPath) {
|
|
1938
2440
|
console.log(chalk.red('✗ No repository configured'));
|
|
@@ -1940,6 +2442,20 @@ async function handleAutoStart(options) {
|
|
|
1940
2442
|
return;
|
|
1941
2443
|
}
|
|
1942
2444
|
|
|
2445
|
+
// Start Auto Mode status tracking
|
|
2446
|
+
const config = await getAutoConfig();
|
|
2447
|
+
|
|
2448
|
+
// Save extension to config if provided
|
|
2449
|
+
if (options.extension) {
|
|
2450
|
+
config.extension = options.extension;
|
|
2451
|
+
await setAutoConfig(config);
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
await startAutoMode(repoPath, { ide: options.ide || config.ide });
|
|
2455
|
+
|
|
2456
|
+
// Also load configured stages here since we already have the config
|
|
2457
|
+
configuredStages = await getStages();
|
|
2458
|
+
|
|
1943
2459
|
console.log(chalk.white(t('auto.repository')), chalk.cyan(repoPath));
|
|
1944
2460
|
|
|
1945
2461
|
// Use the agent that was already determined by provider preferences in interactive.js
|
|
@@ -1954,11 +2470,13 @@ async function handleAutoStart(options) {
|
|
|
1954
2470
|
|
|
1955
2471
|
console.log(chalk.white('Provider:'), chalk.cyan(providerConfig.displayName));
|
|
1956
2472
|
|
|
1957
|
-
// Get max chats
|
|
1958
|
-
const config = await getAutoConfig();
|
|
2473
|
+
// Get max chats (use already loaded config)
|
|
1959
2474
|
const unlimited = !options.maxChats && !config.maxChats && config.neverStop;
|
|
1960
2475
|
const maxChats = unlimited ? Number.MAX_SAFE_INTEGER : (options.maxChats || config.maxChats || 1);
|
|
1961
2476
|
console.log(chalk.white(`${t('auto.direct.config.max.iterations')}`), unlimited ? chalk.cyan('∞ (never stop)') : chalk.cyan(maxChats));
|
|
2477
|
+
|
|
2478
|
+
// Update initial status
|
|
2479
|
+
await updateAutoModeStatus(repoPath, { chatCount: 0, maxChats: unlimited ? 0 : maxChats });
|
|
1962
2480
|
console.log();
|
|
1963
2481
|
console.log(chalk.gray('═'.repeat(80)));
|
|
1964
2482
|
|
|
@@ -1969,6 +2487,9 @@ async function handleAutoStart(options) {
|
|
|
1969
2487
|
// Main loop
|
|
1970
2488
|
let completedCount = 0;
|
|
1971
2489
|
let failedCount = 0;
|
|
2490
|
+
let providerAttempts = 0; // Track attempts for current requirement
|
|
2491
|
+
let lastRequirementText = null; // Track which requirement we're on
|
|
2492
|
+
const MAX_PROVIDER_ATTEMPTS = 3; // Maximum times to try different providers for same requirement
|
|
1972
2493
|
|
|
1973
2494
|
for (let i = 0; i < maxChats; i++) {
|
|
1974
2495
|
// Get current requirement first to check if there are any TODO items
|
|
@@ -1979,6 +2500,32 @@ async function handleAutoStart(options) {
|
|
|
1979
2500
|
break;
|
|
1980
2501
|
}
|
|
1981
2502
|
|
|
2503
|
+
// Check if this is a new requirement or the same one we're retrying
|
|
2504
|
+
if (requirement.text !== lastRequirementText) {
|
|
2505
|
+
// New requirement - reset attempt counter
|
|
2506
|
+
providerAttempts = 0;
|
|
2507
|
+
lastRequirementText = requirement.text;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
// Increment attempt counter
|
|
2511
|
+
providerAttempts++;
|
|
2512
|
+
|
|
2513
|
+
// Check if we've exceeded maximum attempts for this requirement
|
|
2514
|
+
if (providerAttempts > MAX_PROVIDER_ATTEMPTS) {
|
|
2515
|
+
console.log(chalk.red(`\n✗ Maximum provider attempts (${MAX_PROVIDER_ATTEMPTS}) reached for this requirement`));
|
|
2516
|
+
console.log(chalk.yellow(' All available providers have failed or are rate limited'));
|
|
2517
|
+
console.log(chalk.gray(' Skipping this requirement and moving to next...\n'));
|
|
2518
|
+
|
|
2519
|
+
// Mark requirement as failed and move on
|
|
2520
|
+
failedCount++;
|
|
2521
|
+
providerAttempts = 0;
|
|
2522
|
+
lastRequirementText = null;
|
|
2523
|
+
|
|
2524
|
+
// Move requirement to a "Failed" or "Needs Review" section
|
|
2525
|
+
// For now, just continue to next iteration without decrementing i
|
|
2526
|
+
continue;
|
|
2527
|
+
}
|
|
2528
|
+
|
|
1982
2529
|
// Calculate current requirement number consistently (before processing)
|
|
1983
2530
|
// This represents which requirement we're working on (1-based)
|
|
1984
2531
|
const currentReqNumber = completedCount + failedCount + 1;
|
|
@@ -1987,11 +2534,16 @@ async function handleAutoStart(options) {
|
|
|
1987
2534
|
console.log(chalk.bold.magenta(` ${t('auto.direct.requirement.header', { current: currentReqNumber, total: initialEffectiveMax })}`));
|
|
1988
2535
|
console.log(chalk.bold.magenta(`${'━'.repeat(80)}\n`));
|
|
1989
2536
|
|
|
2537
|
+
// Update Auto Mode status with current iteration
|
|
2538
|
+
await updateAutoModeStatus(repoPath, { chatCount: currentReqNumber });
|
|
2539
|
+
|
|
1990
2540
|
// Run iteration with full workflow
|
|
1991
2541
|
const result = await runIteration(requirement, providerConfig, repoPath);
|
|
1992
2542
|
|
|
1993
2543
|
if (result.success) {
|
|
1994
2544
|
completedCount++;
|
|
2545
|
+
providerAttempts = 0; // Reset attempts on success
|
|
2546
|
+
lastRequirementText = null;
|
|
1995
2547
|
console.log(chalk.bold.green(`✅ Requirement ${currentReqNumber}/${initialEffectiveMax} COMPLETE`));
|
|
1996
2548
|
console.log(chalk.gray('Moving to next requirement...\n'));
|
|
1997
2549
|
|
|
@@ -2027,6 +2579,7 @@ async function handleAutoStart(options) {
|
|
|
2027
2579
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
2028
2580
|
|
|
2029
2581
|
// Exit this process - child continues with terminal
|
|
2582
|
+
await stopAutoMode('restarting');
|
|
2030
2583
|
process.exit(0);
|
|
2031
2584
|
} else {
|
|
2032
2585
|
// Small delay before next iteration (if not restarting)
|
|
@@ -2044,7 +2597,7 @@ async function handleAutoStart(options) {
|
|
|
2044
2597
|
|
|
2045
2598
|
console.log(chalk.yellow(`⚠️ ${errorType} detected, switching to next provider in your list...\n`));
|
|
2046
2599
|
|
|
2047
|
-
const newProviderConfig = await acquireProviderConfig(providerConfig.provider);
|
|
2600
|
+
const newProviderConfig = await acquireProviderConfig(providerConfig.provider, providerConfig.model);
|
|
2048
2601
|
if (newProviderConfig) {
|
|
2049
2602
|
providerConfig = newProviderConfig;
|
|
2050
2603
|
console.log(chalk.yellow(`⚠️ ${failedProvider} hit ${errorType.toLowerCase()}`));
|
|
@@ -2085,14 +2638,20 @@ async function handleAutoStart(options) {
|
|
|
2085
2638
|
console.log(chalk.bold.green(`🎉 ${t('auto.direct.summary.final.message', { count: completedCount, plural: completedCount > 1 ? 's' : '' })}`));
|
|
2086
2639
|
}
|
|
2087
2640
|
|
|
2641
|
+
// Stop Auto Mode status tracking
|
|
2642
|
+
await stopAutoMode('completed');
|
|
2643
|
+
|
|
2088
2644
|
} catch (error) {
|
|
2089
2645
|
console.error(chalk.red('\n' + t('auto.fatal.error')), error.message);
|
|
2090
2646
|
if (error.stack) {
|
|
2091
2647
|
console.log(chalk.gray(error.stack));
|
|
2092
2648
|
}
|
|
2649
|
+
|
|
2650
|
+
// Stop Auto Mode status tracking on fatal error
|
|
2651
|
+
await stopAutoMode('error');
|
|
2093
2652
|
process.exit(1);
|
|
2094
2653
|
}
|
|
2095
2654
|
}
|
|
2096
2655
|
|
|
2097
|
-
module.exports = { handleAutoStart };
|
|
2656
|
+
module.exports = { handleAutoStart, waitForIdeCompletion, acquireProviderConfig };
|
|
2098
2657
|
|