vibecodingmachine-cli 2025.12.25-25 → 2026.1.22-1441
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 +92 -118
- package/logs/audit/2025-12-27.jsonl +1 -0
- package/logs/audit/2026-01-03.jsonl +2 -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/auth.js +5 -1
- package/src/commands/auto-direct.js +747 -182
- package/src/commands/auto.js +206 -48
- package/src/commands/computers.js +9 -0
- package/src/commands/feature.js +123 -0
- package/src/commands/ide.js +108 -3
- package/src/commands/repo.js +27 -22
- package/src/commands/requirements-remote.js +34 -2
- package/src/commands/requirements.js +129 -9
- package/src/commands/setup.js +2 -1
- package/src/commands/status.js +39 -1
- package/src/commands/sync.js +7 -1
- package/src/utils/antigravity-js-handler.js +13 -4
- package/src/utils/auth.js +56 -25
- package/src/utils/compliance-check.js +10 -0
- package/src/utils/config.js +42 -1
- package/src/utils/date-formatter.js +44 -0
- package/src/utils/first-run.js +8 -6
- package/src/utils/interactive.js +1363 -334
- package/src/utils/kiro-js-handler.js +188 -0
- package/src/utils/prompt-helper.js +64 -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/home-bootstrap.test.js +76 -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,6 +406,11 @@ async function moveRequirementToVerify(repoPath, requirementText) {
|
|
|
378
406
|
|
|
379
407
|
const content = await fs.readFile(reqPath, 'utf8');
|
|
380
408
|
const lines = content.split('\n');
|
|
409
|
+
|
|
410
|
+
// Get computer filter from config
|
|
411
|
+
const { getComputerFilter } = require('../utils/config');
|
|
412
|
+
const computerFilter = await getComputerFilter();
|
|
413
|
+
|
|
381
414
|
// Find the requirement by its title (in ### header format)
|
|
382
415
|
// Only look in TODO section
|
|
383
416
|
const normalizedRequirement = requirementText.trim();
|
|
@@ -469,175 +502,227 @@ async function moveRequirementToVerify(repoPath, requirementText) {
|
|
|
469
502
|
|
|
470
503
|
// Check if we found a requirement in TODO section
|
|
471
504
|
if (inTodoSection && line.startsWith('###')) {
|
|
472
|
-
|
|
473
|
-
|
|
505
|
+
const title = line.replace(/^###\s*/, '').trim();
|
|
506
|
+
if (title && title.includes(computerFilter)) {
|
|
507
|
+
hasMoreTodoRequirements = true;
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
474
510
|
}
|
|
475
511
|
}
|
|
476
512
|
|
|
477
513
|
// If no more TODO requirements, log message
|
|
478
514
|
if (!hasMoreTodoRequirements) {
|
|
479
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);
|
|
480
519
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
520
|
+
// Find or create TO VERIFY BY HUMAN section with conflict resolution
|
|
521
|
+
// IMPORTANT: Do NOT match VERIFIED sections - only match TO VERIFY sections
|
|
522
|
+
const verifySectionVariants = [
|
|
523
|
+
'## 🔍 TO VERIFY BY HUMAN',
|
|
524
|
+
'## 🔍 TO VERIFY',
|
|
525
|
+
'## TO VERIFY',
|
|
526
|
+
'## ✅ TO VERIFY',
|
|
527
|
+
'## ✅ Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG',
|
|
528
|
+
'## 📊 CROSS-COMPUTER REQUIREMENT ASSIGNMENT',
|
|
529
|
+
'## 🖥️ COMPUTER FOCUS AREA MANAGEMENT',
|
|
530
|
+
'## 🔍 spec-kit: TO VERIFY BY HUMAN'
|
|
531
|
+
];
|
|
532
|
+
|
|
533
|
+
let verifyIndex = -1;
|
|
534
|
+
for (let i = 0; i < lines.length; i++) {
|
|
535
|
+
const line = lines[i];
|
|
536
|
+
const trimmed = line.trim();
|
|
537
|
+
|
|
538
|
+
// Check each variant more carefully
|
|
539
|
+
for (const variant of verifySectionVariants) {
|
|
540
|
+
const variantTrimmed = variant.trim();
|
|
541
|
+
// Exact match or line starts with variant
|
|
542
|
+
if (trimmed === variantTrimmed || trimmed.startsWith(variantTrimmed)) {
|
|
543
|
+
// Double-check: make sure it's NOT a VERIFIED section (without TO VERIFY)
|
|
544
|
+
if (!trimmed.includes('## 📝 VERIFIED') && !trimmed.match(/^##\s+VERIFIED$/i) &&
|
|
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'))) {
|
|
546
|
+
verifyIndex = i;
|
|
547
|
+
break;
|
|
497
548
|
}
|
|
498
549
|
}
|
|
550
|
+
}
|
|
551
|
+
if (verifyIndex !== -1) break;
|
|
552
|
+
}
|
|
499
553
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
'## TO VERIFY'
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
554
|
+
if (verifyIndex === -1) {
|
|
555
|
+
// Create TO VERIFY section - place it BEFORE VERIFIED section if one exists, otherwise before CHANGELOG
|
|
556
|
+
const verifiedIndex = lines.findIndex(line => {
|
|
557
|
+
const trimmed = line.trim();
|
|
558
|
+
return trimmed === '## 📝 VERIFIED' || trimmed.startsWith('## 📝 VERIFIED') ||
|
|
559
|
+
(trimmed.startsWith('##') && trimmed.includes('VERIFIED') && !trimmed.includes('TO VERIFY'));
|
|
560
|
+
});
|
|
561
|
+
const changelogIndex = lines.findIndex(line => line.includes('## CHANGELOG'));
|
|
562
|
+
const manualFeedbackIndex = lines.findIndex(line => line.trim().startsWith('## ❓'));
|
|
563
|
+
|
|
564
|
+
// Prefer: before VERIFIED > before CHANGELOG > before manual feedback > at end
|
|
565
|
+
let insertionIndex = lines.length;
|
|
566
|
+
if (verifiedIndex > 0) {
|
|
567
|
+
insertionIndex = verifiedIndex;
|
|
568
|
+
} else if (changelogIndex > 0) {
|
|
569
|
+
insertionIndex = changelogIndex;
|
|
570
|
+
} else if (manualFeedbackIndex > 0) {
|
|
571
|
+
insertionIndex = manualFeedbackIndex;
|
|
572
|
+
}
|
|
509
573
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
574
|
+
const block = [];
|
|
575
|
+
if (insertionIndex > 0 && lines[insertionIndex - 1].trim() !== '') {
|
|
576
|
+
block.push('');
|
|
577
|
+
}
|
|
578
|
+
block.push('## 🔍 spec-kit: TO VERIFY BY HUMAN', '### Automatic Registration and Tracking', '### User Registration', '');
|
|
579
|
+
block.push('## 📊 CROSS-COMPUTER REQUIREMENT ASSIGNMENT');
|
|
580
|
+
block.push('## 🖥️ COMPUTER FOCUS AREA MANAGEMENT');
|
|
581
|
+
lines.splice(insertionIndex, 0, ...block);
|
|
582
|
+
verifyIndex = lines.findIndex(line => {
|
|
583
|
+
const trimmed = line.trim();
|
|
584
|
+
return trimmed === '## 🔍 spec-kit: TO VERIFY BY HUMAN' || trimmed.startsWith('## 🔍 spec-kit: TO VERIFY BY HUMAN');
|
|
585
|
+
});
|
|
514
586
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if (!trimmed.includes('## 📝 VERIFIED') && !trimmed.match(/^##\s+VERIFIED$/i) &&
|
|
522
|
-
(trimmed.includes('TO VERIFY') || trimmed.includes('Verified by AI screenshot'))) {
|
|
523
|
-
verifyIndex = i;
|
|
524
|
-
break;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
if (verifyIndex !== -1) break;
|
|
529
|
-
}
|
|
587
|
+
// Safety check: verifyIndex should be valid
|
|
588
|
+
if (verifyIndex === -1) {
|
|
589
|
+
console.error(t('auto.direct.verify.section.create.failed'));
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
530
593
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
});
|
|
538
|
-
const changelogIndex = lines.findIndex(line => line.includes('## CHANGELOG'));
|
|
539
|
-
const manualFeedbackIndex = lines.findIndex(line => line.trim().startsWith('## ❓'));
|
|
540
|
-
|
|
541
|
-
// Prefer: before VERIFIED > before CHANGELOG > before manual feedback > at end
|
|
542
|
-
let insertionIndex = lines.length;
|
|
543
|
-
if (verifiedIndex > 0) {
|
|
544
|
-
insertionIndex = verifiedIndex;
|
|
545
|
-
} else if (changelogIndex > 0) {
|
|
546
|
-
insertionIndex = changelogIndex;
|
|
547
|
-
} else if (manualFeedbackIndex > 0) {
|
|
548
|
-
insertionIndex = manualFeedbackIndex;
|
|
549
|
-
}
|
|
594
|
+
// Safety check: verify we're not inserting into a VERIFIED section
|
|
595
|
+
const verifyLine = lines[verifyIndex] || '';
|
|
596
|
+
if (verifyLine.includes('## 📝 VERIFIED') || (verifyLine.trim().startsWith('##') && verifyLine.includes('VERIFIED') && !verifyLine.includes('TO VERIFY'))) {
|
|
597
|
+
console.error('ERROR: Attempted to insert into VERIFIED section instead of TO VERIFY');
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
550
600
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
601
|
+
// Remove any existing duplicate of this requirement in TO VERIFY section
|
|
602
|
+
// Find the next section header after TO VERIFY
|
|
603
|
+
let nextSectionIndex = lines.length;
|
|
604
|
+
for (let i = verifyIndex + 1; i < lines.length; i++) {
|
|
605
|
+
const trimmed = lines[i].trim();
|
|
606
|
+
if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
607
|
+
nextSectionIndex = i;
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
561
611
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
612
|
+
// Search for duplicate requirement in TO VERIFY section
|
|
613
|
+
const requirementTitle = requirementBlock[0].trim().replace(/^###\s*/, '').trim();
|
|
614
|
+
for (let i = verifyIndex + 1; i < nextSectionIndex; i++) {
|
|
615
|
+
const line = lines[i];
|
|
616
|
+
const trimmed = line.trim();
|
|
617
|
+
|
|
618
|
+
if (trimmed.startsWith('###')) {
|
|
619
|
+
const existingTitle = trimmed.replace(/^###\s*/, '').trim();
|
|
620
|
+
|
|
621
|
+
// Check if this is a duplicate (exact match or contains/contained by)
|
|
622
|
+
if (existingTitle === requirementTitle ||
|
|
623
|
+
existingTitle.includes(requirementTitle) ||
|
|
624
|
+
requirementTitle.includes(existingTitle)) {
|
|
625
|
+
|
|
626
|
+
// Find the end of this existing requirement
|
|
627
|
+
let existingEndIndex = nextSectionIndex;
|
|
628
|
+
for (let j = i + 1; j < nextSectionIndex; j++) {
|
|
629
|
+
const nextLine = lines[j].trim();
|
|
630
|
+
if (nextLine.startsWith('###') || nextLine.startsWith('##')) {
|
|
631
|
+
existingEndIndex = j;
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
566
634
|
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
635
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (verifyLine.includes('## 📝 VERIFIED') || (verifyLine.trim().startsWith('##') && verifyLine.includes('VERIFIED') && !verifyLine.includes('TO VERIFY'))) {
|
|
573
|
-
console.error('ERROR: Attempted to insert into VERIFIED section instead of TO VERIFY');
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
636
|
+
// Remove the duplicate
|
|
637
|
+
lines.splice(i, existingEndIndex - i);
|
|
576
638
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
for (let i = verifyIndex + 1; i < lines.length; i++) {
|
|
581
|
-
const trimmed = lines[i].trim();
|
|
582
|
-
if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
583
|
-
nextSectionIndex = i;
|
|
584
|
-
break;
|
|
585
|
-
}
|
|
639
|
+
// Adjust nextSectionIndex if needed
|
|
640
|
+
nextSectionIndex -= (existingEndIndex - i);
|
|
641
|
+
break;
|
|
586
642
|
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
587
645
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
for (let i = verifyIndex + 1; i < nextSectionIndex; i++) {
|
|
591
|
-
const line = lines[i];
|
|
592
|
-
const trimmed = line.trim();
|
|
646
|
+
// Insert requirement block at TOP of TO VERIFY section (right after section header)
|
|
647
|
+
let insertIndex = verifyIndex + 1;
|
|
593
648
|
|
|
594
|
-
|
|
595
|
-
|
|
649
|
+
// Ensure there's a blank line after the section header
|
|
650
|
+
if (lines[insertIndex]?.trim() !== '') {
|
|
651
|
+
lines.splice(insertIndex, 0, '');
|
|
652
|
+
insertIndex++;
|
|
653
|
+
}
|
|
596
654
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
+
}
|
|
601
675
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
for (let j = i + 1; j < nextSectionIndex; j++) {
|
|
605
|
-
const nextLine = lines[j].trim();
|
|
606
|
-
if (nextLine.startsWith('###') || nextLine.startsWith('##')) {
|
|
607
|
-
existingEndIndex = j;
|
|
608
|
-
break;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
676
|
+
// Insert the requirement block
|
|
677
|
+
lines.splice(insertIndex, 0, ...requirementBlock);
|
|
611
678
|
|
|
612
|
-
|
|
613
|
-
|
|
679
|
+
// Ensure there's a blank line after the requirement block
|
|
680
|
+
const afterIndex = insertIndex + requirementBlock.length;
|
|
681
|
+
if (afterIndex < lines.length && lines[afterIndex]?.trim() !== '') {
|
|
682
|
+
lines.splice(afterIndex, 0, '');
|
|
683
|
+
}
|
|
614
684
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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;
|
|
620
702
|
}
|
|
703
|
+
}
|
|
704
|
+
if (verifiedIndex !== -1) break;
|
|
705
|
+
}
|
|
621
706
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
+
}
|
|
625
717
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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);
|
|
631
724
|
|
|
632
|
-
// Insert the requirement block with image paste ability
|
|
633
|
-
const imagePaste = '### Image Paste\n\nYou can paste an image here by using the following format: \n\nExample: ';
|
|
634
|
-
lines.splice(insertIndex, 0, ...requirementBlock, imagePaste);
|
|
635
725
|
|
|
636
|
-
// Ensure there's a blank line after the requirement block
|
|
637
|
-
const afterIndex = insertIndex + requirementBlock.length + 1;
|
|
638
|
-
if (afterIndex < lines.length && lines[afterIndex]?.trim() !== '') {
|
|
639
|
-
lines.splice(afterIndex, 0, '');
|
|
640
|
-
}
|
|
641
726
|
|
|
642
727
|
// Write the file
|
|
643
728
|
await fs.writeFile(reqPath, lines.join('\n'));
|
|
@@ -793,8 +878,31 @@ async function getAllAvailableProviders() {
|
|
|
793
878
|
case 'windsurf':
|
|
794
879
|
case 'vscode':
|
|
795
880
|
case 'kiro':
|
|
796
|
-
case 'antigravity':
|
|
797
|
-
|
|
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
|
+
}
|
|
798
906
|
break;
|
|
799
907
|
}
|
|
800
908
|
case 'ollama': {
|
|
@@ -829,7 +937,49 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
829
937
|
const config = await getAutoConfig();
|
|
830
938
|
const providerManager = sharedProviderManager; // Use shared instance to persist rate limit state
|
|
831
939
|
const providers = await getAllAvailableProviders();
|
|
832
|
-
const
|
|
940
|
+
const prefs = await getProviderPreferences();
|
|
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
|
+
|
|
967
|
+
// Get first ENABLED agent from provider preferences (same logic as interactive menu)
|
|
968
|
+
let firstEnabledAgent = null;
|
|
969
|
+
for (const agentId of prefs.order) {
|
|
970
|
+
if (prefs.enabled[agentId] !== false) {
|
|
971
|
+
firstEnabledAgent = agentId;
|
|
972
|
+
break;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
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
|
+
}
|
|
833
983
|
|
|
834
984
|
if (providers.length === 0) {
|
|
835
985
|
return { status: 'no_providers', providers: [] };
|
|
@@ -845,17 +995,147 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
845
995
|
const availableProviders = enabledProviders.filter(p => {
|
|
846
996
|
// Exclude the specified provider and rate-limited providers
|
|
847
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
|
+
}
|
|
848
1001
|
return false;
|
|
849
1002
|
}
|
|
850
|
-
|
|
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;
|
|
851
1038
|
});
|
|
852
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
|
+
|
|
853
1099
|
let selection = null;
|
|
854
1100
|
if (savedAgent && savedAgent !== excludeProvider) {
|
|
855
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
|
+
}
|
|
856
1106
|
}
|
|
857
|
-
|
|
858
|
-
|
|
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
|
+
}
|
|
859
1139
|
}
|
|
860
1140
|
|
|
861
1141
|
if (selection) {
|
|
@@ -879,10 +1159,16 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
879
1159
|
};
|
|
880
1160
|
}
|
|
881
1161
|
|
|
882
|
-
async function acquireProviderConfig(excludeProvider = null) {
|
|
1162
|
+
async function acquireProviderConfig(excludeProvider = null, excludeModel = null) {
|
|
883
1163
|
while (true) {
|
|
884
1164
|
const selection = await getProviderConfig(excludeProvider);
|
|
885
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
|
+
}
|
|
886
1172
|
return selection.provider;
|
|
887
1173
|
}
|
|
888
1174
|
|
|
@@ -1329,6 +1615,13 @@ async function runIdeProviderIteration(providerConfig, repoPath) {
|
|
|
1329
1615
|
console.log(chalk.cyan(`⚙️ ${t('auto.direct.provider.launching', { provider: providerConfig.displayName })}\n`));
|
|
1330
1616
|
|
|
1331
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
|
+
}
|
|
1332
1625
|
const child = spawn(process.execPath, args, {
|
|
1333
1626
|
cwd: repoPath,
|
|
1334
1627
|
env: process.env,
|
|
@@ -1363,13 +1656,15 @@ async function runIdeProviderIteration(providerConfig, repoPath) {
|
|
|
1363
1656
|
} else {
|
|
1364
1657
|
const message = `${providerConfig.displayName} exited with code ${code}`;
|
|
1365
1658
|
const antigravityRateLimit = checkAntigravityRateLimit(combinedOutput);
|
|
1659
|
+
const kiroRateLimit = checkKiroRateLimit(combinedOutput);
|
|
1366
1660
|
|
|
1367
1661
|
resolve({
|
|
1368
1662
|
success: false,
|
|
1369
1663
|
error: combinedOutput ? `${message}\n${combinedOutput}` : message,
|
|
1370
1664
|
output: combinedOutput,
|
|
1371
|
-
rateLimited: isRateLimitMessage(combinedOutput) || antigravityRateLimit.isRateLimited,
|
|
1372
|
-
antigravityRateLimited: antigravityRateLimit.isRateLimited
|
|
1665
|
+
rateLimited: isRateLimitMessage(combinedOutput) || antigravityRateLimit.isRateLimited || kiroRateLimit.isRateLimited,
|
|
1666
|
+
antigravityRateLimited: antigravityRateLimit.isRateLimited,
|
|
1667
|
+
kiroRateLimited: kiroRateLimit.isRateLimited
|
|
1373
1668
|
});
|
|
1374
1669
|
}
|
|
1375
1670
|
});
|
|
@@ -1412,7 +1707,7 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
1412
1707
|
let foundInVerified = false;
|
|
1413
1708
|
|
|
1414
1709
|
for (const line of lines) {
|
|
1415
|
-
if (line.includes('## ✅ Verified by AI
|
|
1710
|
+
if (line.includes('## ✅ Verified by AI')) {
|
|
1416
1711
|
inVerifiedSection = true;
|
|
1417
1712
|
continue;
|
|
1418
1713
|
}
|
|
@@ -1442,6 +1737,10 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
1442
1737
|
for (const line of lines) {
|
|
1443
1738
|
if (line.includes('🚦 Current Status')) {
|
|
1444
1739
|
inStatusSection = true;
|
|
1740
|
+
if (line.includes('DONE')) {
|
|
1741
|
+
statusContainsDone = true;
|
|
1742
|
+
break;
|
|
1743
|
+
}
|
|
1445
1744
|
continue;
|
|
1446
1745
|
}
|
|
1447
1746
|
|
|
@@ -1463,21 +1762,72 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
1463
1762
|
return;
|
|
1464
1763
|
}
|
|
1465
1764
|
|
|
1466
|
-
// Check 3:
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
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
|
+
|
|
1471
1787
|
watcher.close();
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
antigravityRateLimited: true // This triggers switching to next provider
|
|
1476
|
-
});
|
|
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 });
|
|
1477
1791
|
return;
|
|
1478
1792
|
}
|
|
1479
1793
|
|
|
1480
|
-
// 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;
|
|
1481
1831
|
if (elapsed >= timeoutMs) {
|
|
1482
1832
|
watcher.close();
|
|
1483
1833
|
console.log(chalk.yellow(`\n⚠️ Timeout after ${Math.floor(elapsed / 60000)} minutes\n`));
|
|
@@ -1532,30 +1882,110 @@ async function runIdeFallbackIteration(requirement, providerConfig, repoPath, pr
|
|
|
1532
1882
|
|
|
1533
1883
|
if (!ideResult.success) {
|
|
1534
1884
|
if (ideResult.antigravityRateLimited) {
|
|
1535
|
-
|
|
1536
|
-
|
|
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 };
|
|
1537
1891
|
}
|
|
1892
|
+
|
|
1538
1893
|
// CRITICAL: Mark provider as unavailable for ANY error so acquireProviderConfig() will skip it
|
|
1539
|
-
|
|
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
|
+
|
|
1540
1907
|
return { success: false, error: ideResult.error || 'IDE provider failed' };
|
|
1541
1908
|
}
|
|
1542
1909
|
|
|
1543
1910
|
console.log(chalk.green(`✓ ${t('auto.direct.ide.prompt.sent')}`));
|
|
1544
1911
|
|
|
1545
1912
|
// Wait for IDE agent to complete the work (IDE will update status to DONE itself)
|
|
1913
|
+
const ideCompletionStartTime = Date.now();
|
|
1546
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
|
+
}
|
|
1547
1942
|
|
|
1548
1943
|
if (!completionResult.success) {
|
|
1944
|
+
// Special-case behavior for Antigravity CLI installs (they have special handling)
|
|
1549
1945
|
if (completionResult.antigravityRateLimited) {
|
|
1550
1946
|
console.log(chalk.yellow(`⚠️ ${t('auto.direct.provider.quota.exhausted', { provider: 'Antigravity' })}\n`));
|
|
1551
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
|
+
|
|
1552
1954
|
return { success: false, error: 'Antigravity quota limit', shouldRetry: true };
|
|
1553
1955
|
}
|
|
1554
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
|
+
|
|
1555
1978
|
const errorMsg = completionResult.reason === 'timeout'
|
|
1556
1979
|
? 'IDE agent timed out'
|
|
1557
1980
|
: 'IDE agent failed to complete';
|
|
1558
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
|
+
|
|
1559
1989
|
return { success: false, error: errorMsg };
|
|
1560
1990
|
}
|
|
1561
1991
|
|
|
@@ -1624,7 +2054,10 @@ async function runIteration(requirement, providerConfig, repoPath) {
|
|
|
1624
2054
|
// ═══════════════════════════════════════════════════════════
|
|
1625
2055
|
printStatusCard(requirement.text, 'ACT');
|
|
1626
2056
|
|
|
1627
|
-
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)));
|
|
1628
2061
|
|
|
1629
2062
|
// Build context with actual file snippets
|
|
1630
2063
|
let contextSection = '';
|
|
@@ -1743,27 +2176,97 @@ if (counts.todoCount === 0) {
|
|
|
1743
2176
|
Now implement the requirement. Remember: COPY THE SEARCH BLOCK EXACTLY!`;
|
|
1744
2177
|
|
|
1745
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();
|
|
1746
2205
|
|
|
1747
2206
|
const result = await llm.call(providerConfig, prompt, {
|
|
1748
2207
|
temperature: 0.1,
|
|
1749
2208
|
maxTokens: 4096,
|
|
1750
2209
|
onChunk: (chunk) => {
|
|
1751
|
-
|
|
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));
|
|
1752
2220
|
fullResponse += chunk;
|
|
1753
2221
|
},
|
|
1754
2222
|
onComplete: () => {
|
|
1755
|
-
|
|
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();
|
|
1756
2231
|
},
|
|
1757
2232
|
onError: (error) => {
|
|
2233
|
+
stopSpinner();
|
|
1758
2234
|
console.error(chalk.red(`\n✗ Error: ${error}`));
|
|
1759
2235
|
}
|
|
1760
2236
|
});
|
|
1761
2237
|
|
|
2238
|
+
// Ensure spinner is stopped even if callbacks didn't fire
|
|
2239
|
+
stopSpinner();
|
|
2240
|
+
|
|
1762
2241
|
if (!result.success) {
|
|
1763
2242
|
const combinedError = [result.error, fullResponse].filter(Boolean).join('\n').trim() || 'Unknown error';
|
|
1764
2243
|
console.log(chalk.red(`\n✗ LLM call failed: ${combinedError}`));
|
|
1765
2244
|
// CRITICAL: Mark provider as rate-limited for ANY error so acquireProviderConfig() will skip it
|
|
1766
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
|
+
|
|
1767
2270
|
return { success: false, error: combinedError };
|
|
1768
2271
|
}
|
|
1769
2272
|
|
|
@@ -1890,6 +2393,18 @@ Now implement the requirement. Remember: COPY THE SEARCH BLOCK EXACTLY!`;
|
|
|
1890
2393
|
const duration = Date.now() - startTime;
|
|
1891
2394
|
providerManager.recordPerformance(providerConfig.provider, providerConfig.model, duration);
|
|
1892
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
|
+
|
|
1893
2408
|
console.log();
|
|
1894
2409
|
console.log(chalk.gray('─'.repeat(80)));
|
|
1895
2410
|
console.log();
|
|
@@ -1920,10 +2435,6 @@ async function handleAutoStart(options) {
|
|
|
1920
2435
|
console.log(chalk.gray('═'.repeat(80)));
|
|
1921
2436
|
console.log();
|
|
1922
2437
|
|
|
1923
|
-
// Get repo path
|
|
1924
|
-
// Load configured stages
|
|
1925
|
-
configuredStages = await getStages();
|
|
1926
|
-
|
|
1927
2438
|
const repoPath = await getRepoPath();
|
|
1928
2439
|
if (!repoPath) {
|
|
1929
2440
|
console.log(chalk.red('✗ No repository configured'));
|
|
@@ -1931,14 +2442,25 @@ async function handleAutoStart(options) {
|
|
|
1931
2442
|
return;
|
|
1932
2443
|
}
|
|
1933
2444
|
|
|
1934
|
-
|
|
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();
|
|
1935
2458
|
|
|
1936
|
-
|
|
1937
|
-
const { getEffectiveAgent } = require('../utils/agent-selector');
|
|
1938
|
-
const { PROVIDER_DEFINITIONS } = require('../utils/provider-registry');
|
|
1939
|
-
const PROVIDER_DEFINITION_MAP = new Map(PROVIDER_DEFINITIONS.map(def => [def.id, def]));
|
|
2459
|
+
console.log(chalk.white(t('auto.repository')), chalk.cyan(repoPath));
|
|
1940
2460
|
|
|
1941
|
-
|
|
2461
|
+
// Use the agent that was already determined by provider preferences in interactive.js
|
|
2462
|
+
// No need to call getEffectiveAgent since we already have the correct agent
|
|
2463
|
+
const effectiveAgent = options.ide;
|
|
1942
2464
|
|
|
1943
2465
|
// Get provider configuration for the selected agent
|
|
1944
2466
|
let providerConfig = await acquireProviderConfig(effectiveAgent);
|
|
@@ -1948,11 +2470,13 @@ async function handleAutoStart(options) {
|
|
|
1948
2470
|
|
|
1949
2471
|
console.log(chalk.white('Provider:'), chalk.cyan(providerConfig.displayName));
|
|
1950
2472
|
|
|
1951
|
-
// Get max chats
|
|
1952
|
-
const config = await getAutoConfig();
|
|
2473
|
+
// Get max chats (use already loaded config)
|
|
1953
2474
|
const unlimited = !options.maxChats && !config.maxChats && config.neverStop;
|
|
1954
2475
|
const maxChats = unlimited ? Number.MAX_SAFE_INTEGER : (options.maxChats || config.maxChats || 1);
|
|
1955
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 });
|
|
1956
2480
|
console.log();
|
|
1957
2481
|
console.log(chalk.gray('═'.repeat(80)));
|
|
1958
2482
|
|
|
@@ -1963,6 +2487,9 @@ async function handleAutoStart(options) {
|
|
|
1963
2487
|
// Main loop
|
|
1964
2488
|
let completedCount = 0;
|
|
1965
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
|
|
1966
2493
|
|
|
1967
2494
|
for (let i = 0; i < maxChats; i++) {
|
|
1968
2495
|
// Get current requirement first to check if there are any TODO items
|
|
@@ -1973,6 +2500,32 @@ async function handleAutoStart(options) {
|
|
|
1973
2500
|
break;
|
|
1974
2501
|
}
|
|
1975
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
|
+
|
|
1976
2529
|
// Calculate current requirement number consistently (before processing)
|
|
1977
2530
|
// This represents which requirement we're working on (1-based)
|
|
1978
2531
|
const currentReqNumber = completedCount + failedCount + 1;
|
|
@@ -1981,11 +2534,16 @@ async function handleAutoStart(options) {
|
|
|
1981
2534
|
console.log(chalk.bold.magenta(` ${t('auto.direct.requirement.header', { current: currentReqNumber, total: initialEffectiveMax })}`));
|
|
1982
2535
|
console.log(chalk.bold.magenta(`${'━'.repeat(80)}\n`));
|
|
1983
2536
|
|
|
2537
|
+
// Update Auto Mode status with current iteration
|
|
2538
|
+
await updateAutoModeStatus(repoPath, { chatCount: currentReqNumber });
|
|
2539
|
+
|
|
1984
2540
|
// Run iteration with full workflow
|
|
1985
2541
|
const result = await runIteration(requirement, providerConfig, repoPath);
|
|
1986
2542
|
|
|
1987
2543
|
if (result.success) {
|
|
1988
2544
|
completedCount++;
|
|
2545
|
+
providerAttempts = 0; // Reset attempts on success
|
|
2546
|
+
lastRequirementText = null;
|
|
1989
2547
|
console.log(chalk.bold.green(`✅ Requirement ${currentReqNumber}/${initialEffectiveMax} COMPLETE`));
|
|
1990
2548
|
console.log(chalk.gray('Moving to next requirement...\n'));
|
|
1991
2549
|
|
|
@@ -2021,6 +2579,7 @@ async function handleAutoStart(options) {
|
|
|
2021
2579
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
2022
2580
|
|
|
2023
2581
|
// Exit this process - child continues with terminal
|
|
2582
|
+
await stopAutoMode('restarting');
|
|
2024
2583
|
process.exit(0);
|
|
2025
2584
|
} else {
|
|
2026
2585
|
// Small delay before next iteration (if not restarting)
|
|
@@ -2038,7 +2597,7 @@ async function handleAutoStart(options) {
|
|
|
2038
2597
|
|
|
2039
2598
|
console.log(chalk.yellow(`⚠️ ${errorType} detected, switching to next provider in your list...\n`));
|
|
2040
2599
|
|
|
2041
|
-
const newProviderConfig = await acquireProviderConfig(providerConfig.provider);
|
|
2600
|
+
const newProviderConfig = await acquireProviderConfig(providerConfig.provider, providerConfig.model);
|
|
2042
2601
|
if (newProviderConfig) {
|
|
2043
2602
|
providerConfig = newProviderConfig;
|
|
2044
2603
|
console.log(chalk.yellow(`⚠️ ${failedProvider} hit ${errorType.toLowerCase()}`));
|
|
@@ -2079,14 +2638,20 @@ async function handleAutoStart(options) {
|
|
|
2079
2638
|
console.log(chalk.bold.green(`🎉 ${t('auto.direct.summary.final.message', { count: completedCount, plural: completedCount > 1 ? 's' : '' })}`));
|
|
2080
2639
|
}
|
|
2081
2640
|
|
|
2641
|
+
// Stop Auto Mode status tracking
|
|
2642
|
+
await stopAutoMode('completed');
|
|
2643
|
+
|
|
2082
2644
|
} catch (error) {
|
|
2083
2645
|
console.error(chalk.red('\n' + t('auto.fatal.error')), error.message);
|
|
2084
2646
|
if (error.stack) {
|
|
2085
2647
|
console.log(chalk.gray(error.stack));
|
|
2086
2648
|
}
|
|
2649
|
+
|
|
2650
|
+
// Stop Auto Mode status tracking on fatal error
|
|
2651
|
+
await stopAutoMode('error');
|
|
2087
2652
|
process.exit(1);
|
|
2088
2653
|
}
|
|
2089
2654
|
}
|
|
2090
2655
|
|
|
2091
|
-
module.exports = { handleAutoStart };
|
|
2656
|
+
module.exports = { handleAutoStart, waitForIdeCompletion, acquireProviderConfig };
|
|
2092
2657
|
|