polydev-ai 1.9.14 → 1.9.16
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/mcp/stdio-wrapper.js +105 -317
- package/package.json +1 -1
package/mcp/stdio-wrapper.js
CHANGED
|
@@ -300,6 +300,10 @@ class StdioMCPWrapper {
|
|
|
300
300
|
// Pending session file for surviving restarts
|
|
301
301
|
this.PENDING_SESSION_FILE = path.join(os.homedir(), '.polydev-pending-session');
|
|
302
302
|
|
|
303
|
+
// MCP client info (set during initialize handshake)
|
|
304
|
+
// Used to detect which IDE is running us and exclude its CLI to avoid recursive calls
|
|
305
|
+
this.clientInfo = null;
|
|
306
|
+
|
|
303
307
|
// Initialize CLI Manager for local CLI functionality
|
|
304
308
|
// Disable StatusReporter - it's redundant (updateCliStatusInDatabase handles DB updates via /api/cli-status-update)
|
|
305
309
|
// and causes 401 errors because /api/mcp uses different auth than /api/cli-status-update
|
|
@@ -363,6 +367,12 @@ class StdioMCPWrapper {
|
|
|
363
367
|
try {
|
|
364
368
|
switch (method) {
|
|
365
369
|
case 'initialize':
|
|
370
|
+
// Capture client info to detect which IDE is running us
|
|
371
|
+
// This lets us exclude its CLI to avoid recursive calls (e.g., Claude Code → claude_code CLI)
|
|
372
|
+
this.clientInfo = params?.clientInfo || null;
|
|
373
|
+
if (this.clientInfo) {
|
|
374
|
+
console.error(`[Stdio Wrapper] IDE detected: ${this.clientInfo.name} v${this.clientInfo.version || 'unknown'}`);
|
|
375
|
+
}
|
|
366
376
|
return {
|
|
367
377
|
jsonrpc: '2.0',
|
|
368
378
|
id,
|
|
@@ -990,11 +1000,10 @@ Token will be saved automatically after login.`
|
|
|
990
1000
|
perspectivesSection += ` (${perspectiveCount} active)`;
|
|
991
1001
|
|
|
992
1002
|
// Show providers in order with their selected models
|
|
993
|
-
for (
|
|
994
|
-
const p = modelPrefs.allProviders[i];
|
|
1003
|
+
for (const p of modelPrefs.allProviders) {
|
|
995
1004
|
const providerName = p.provider.charAt(0).toUpperCase() + p.provider.slice(1);
|
|
996
1005
|
const source = p.cliId ? 'CLI' : (p.tier ? `Credits/${p.tier}` : 'API');
|
|
997
|
-
perspectivesSection += `\n ${
|
|
1006
|
+
perspectivesSection += `\n ${providerName.padEnd(12)} ${p.model} [${source}]`;
|
|
998
1007
|
}
|
|
999
1008
|
} else {
|
|
1000
1009
|
perspectivesSection += '\n (none configured - using defaults)';
|
|
@@ -1412,271 +1421,46 @@ Error: ${error.message}`
|
|
|
1412
1421
|
</html>`;
|
|
1413
1422
|
}
|
|
1414
1423
|
|
|
1415
|
-
async forwardToRemoteServer(request) {
|
|
1416
|
-
console.error(`[Stdio Wrapper] Forwarding request to remote server`);
|
|
1417
|
-
|
|
1418
|
-
try {
|
|
1419
|
-
// Use AbortController for timeout if available, otherwise rely on fetch timeout
|
|
1420
|
-
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
1421
|
-
const timeoutId = controller ? setTimeout(() => controller.abort(), 240000) : null; // 240s timeout
|
|
1422
|
-
|
|
1423
|
-
const response = await fetch('https://www.polydev.ai/api/mcp', {
|
|
1424
|
-
method: 'POST',
|
|
1425
|
-
headers: {
|
|
1426
|
-
'Content-Type': 'application/json',
|
|
1427
|
-
'Authorization': `Bearer ${this.userToken}`,
|
|
1428
|
-
'User-Agent': 'polydev-stdio-wrapper/1.0.0'
|
|
1429
|
-
},
|
|
1430
|
-
body: JSON.stringify(request),
|
|
1431
|
-
...(controller ? { signal: controller.signal } : {})
|
|
1432
|
-
});
|
|
1433
|
-
|
|
1434
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
1435
|
-
|
|
1436
|
-
if (!response.ok) {
|
|
1437
|
-
const errorText = await response.text();
|
|
1438
|
-
console.error(`[Stdio Wrapper] Remote server error: ${response.status} - ${errorText}`);
|
|
1439
|
-
|
|
1440
|
-
// Handle 401 specifically - token expired/invalid, trigger re-auth
|
|
1441
|
-
if (response.status === 401) {
|
|
1442
|
-
console.error('[Polydev] Remote API returned 401, auto-triggering re-authentication...');
|
|
1443
|
-
return await this.triggerReAuth(request.id, 'Authentication expired. Re-authenticating...');
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
return {
|
|
1447
|
-
jsonrpc: '2.0',
|
|
1448
|
-
id: request.id,
|
|
1449
|
-
error: {
|
|
1450
|
-
code: -32603,
|
|
1451
|
-
message: `Remote server error: ${response.status} - ${errorText}`
|
|
1452
|
-
}
|
|
1453
|
-
};
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
const result = await response.json();
|
|
1457
|
-
console.error(`[Stdio Wrapper] Got response from remote server`);
|
|
1458
|
-
return result;
|
|
1459
|
-
} catch (error) {
|
|
1460
|
-
console.error(`[Stdio Wrapper] Network error:`, error.message);
|
|
1461
|
-
return {
|
|
1462
|
-
jsonrpc: '2.0',
|
|
1463
|
-
id: request.id,
|
|
1464
|
-
error: {
|
|
1465
|
-
code: -32603,
|
|
1466
|
-
message: `Network error: ${error.message}`
|
|
1467
|
-
}
|
|
1468
|
-
};
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
1424
|
/**
|
|
1473
|
-
*
|
|
1425
|
+
* Get the CLI ID to exclude based on the current IDE client.
|
|
1426
|
+
* Prevents recursive calls (e.g., Claude Code calling claude_code CLI which spawns another Claude Code).
|
|
1427
|
+
* Returns null if no CLI should be excluded.
|
|
1474
1428
|
*/
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
'force_cli_detection',
|
|
1478
|
-
'get_cli_status',
|
|
1479
|
-
'send_cli_prompt',
|
|
1480
|
-
'polydev.force_cli_detection',
|
|
1481
|
-
'polydev.get_cli_status',
|
|
1482
|
-
'polydev.send_cli_prompt'
|
|
1483
|
-
];
|
|
1484
|
-
return cliTools.includes(toolName);
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
/**
|
|
1488
|
-
* Handle get_perspectives with local CLIs + remote perspectives
|
|
1489
|
-
*/
|
|
1490
|
-
async handleGetPerspectivesWithCLIs(params, id) {
|
|
1491
|
-
console.error(`[Stdio Wrapper] Handling get_perspectives with local CLIs + remote`);
|
|
1429
|
+
getExcludedCliForCurrentIDE() {
|
|
1430
|
+
if (!this.clientInfo?.name) return null;
|
|
1492
1431
|
|
|
1493
|
-
|
|
1494
|
-
// Use existing localSendCliPrompt logic which already:
|
|
1495
|
-
// 1. Checks all local CLIs
|
|
1496
|
-
// 2. Calls remote perspectives
|
|
1497
|
-
// 3. Combines results
|
|
1498
|
-
const result = await this.localSendCliPrompt(params.arguments);
|
|
1499
|
-
|
|
1500
|
-
// Check if result indicates auth failure (from forwardToRemoteServer 401 handling)
|
|
1501
|
-
const resultText = result.content || this.formatCliResponse(result);
|
|
1502
|
-
if (result.result?.content?.[0]?.text?.includes('RE-AUTHENTICATION REQUIRED')) {
|
|
1503
|
-
// Auth failure already handled with re-auth response - pass through
|
|
1504
|
-
return result;
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
return {
|
|
1508
|
-
jsonrpc: '2.0',
|
|
1509
|
-
id,
|
|
1510
|
-
result: {
|
|
1511
|
-
content: [
|
|
1512
|
-
{
|
|
1513
|
-
type: 'text',
|
|
1514
|
-
text: resultText
|
|
1515
|
-
}
|
|
1516
|
-
]
|
|
1517
|
-
}
|
|
1518
|
-
};
|
|
1519
|
-
|
|
1520
|
-
} catch (error) {
|
|
1521
|
-
console.error(`[Stdio Wrapper] get_perspectives error:`, error);
|
|
1522
|
-
|
|
1523
|
-
// Check if error is auth-related
|
|
1524
|
-
const errMsg = (error.message || '').toLowerCase();
|
|
1525
|
-
if (errMsg.includes('401') || errMsg.includes('unauthorized') || errMsg.includes('auth') || errMsg.includes('token')) {
|
|
1526
|
-
console.error('[Polydev] Perspectives auth error, auto-triggering re-authentication...');
|
|
1527
|
-
return await this.triggerReAuth(id, 'Authentication expired. Re-authenticating...');
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
return {
|
|
1531
|
-
jsonrpc: '2.0',
|
|
1532
|
-
id,
|
|
1533
|
-
error: {
|
|
1534
|
-
code: -32603,
|
|
1535
|
-
message: error.message
|
|
1536
|
-
}
|
|
1537
|
-
};
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
/**
|
|
1542
|
-
* Handle CLI tools locally without remote server calls
|
|
1543
|
-
*/
|
|
1544
|
-
async handleLocalCliTool(request) {
|
|
1545
|
-
const { method, params, id } = request;
|
|
1546
|
-
const { name: toolName, arguments: args } = params;
|
|
1547
|
-
|
|
1548
|
-
console.error(`[Stdio Wrapper] Handling local CLI tool: ${toolName}`);
|
|
1549
|
-
|
|
1550
|
-
try {
|
|
1551
|
-
let result;
|
|
1552
|
-
|
|
1553
|
-
switch (toolName) {
|
|
1554
|
-
case 'force_cli_detection':
|
|
1555
|
-
case 'polydev.force_cli_detection':
|
|
1556
|
-
result = await this.localForceCliDetection(args);
|
|
1557
|
-
break;
|
|
1558
|
-
|
|
1559
|
-
case 'get_cli_status':
|
|
1560
|
-
case 'polydev.get_cli_status':
|
|
1561
|
-
result = await this.localGetCliStatus(args);
|
|
1562
|
-
break;
|
|
1563
|
-
|
|
1564
|
-
case 'send_cli_prompt':
|
|
1565
|
-
case 'polydev.send_cli_prompt':
|
|
1566
|
-
result = await this.localSendCliPrompt(args);
|
|
1567
|
-
break;
|
|
1568
|
-
|
|
1569
|
-
default:
|
|
1570
|
-
throw new Error(`Unknown CLI tool: ${toolName}`);
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
return {
|
|
1574
|
-
jsonrpc: '2.0',
|
|
1575
|
-
id,
|
|
1576
|
-
result: {
|
|
1577
|
-
content: [
|
|
1578
|
-
{
|
|
1579
|
-
type: 'text',
|
|
1580
|
-
text: this.formatCliResponse(result)
|
|
1581
|
-
}
|
|
1582
|
-
]
|
|
1583
|
-
}
|
|
1584
|
-
};
|
|
1585
|
-
|
|
1586
|
-
} catch (error) {
|
|
1587
|
-
console.error(`[Stdio Wrapper] CLI tool error:`, error);
|
|
1588
|
-
return {
|
|
1589
|
-
jsonrpc: '2.0',
|
|
1590
|
-
id,
|
|
1591
|
-
error: {
|
|
1592
|
-
code: -32603,
|
|
1593
|
-
message: error.message
|
|
1594
|
-
}
|
|
1595
|
-
};
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
/**
|
|
1600
|
-
* Local CLI detection implementation with database updates
|
|
1601
|
-
*/
|
|
1602
|
-
async localForceCliDetection(args) {
|
|
1603
|
-
console.error(`[Stdio Wrapper] Local CLI detection with model detection started`);
|
|
1432
|
+
const clientName = this.clientInfo.name.toLowerCase();
|
|
1604
1433
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
//
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
//
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
local_only: true
|
|
1624
|
-
};
|
|
1625
|
-
|
|
1626
|
-
} catch (error) {
|
|
1627
|
-
console.error('[Stdio Wrapper] Local CLI detection error:', error);
|
|
1628
|
-
return {
|
|
1629
|
-
success: false,
|
|
1630
|
-
error: error.message,
|
|
1631
|
-
timestamp: new Date().toISOString(),
|
|
1632
|
-
local_only: true
|
|
1633
|
-
};
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
/**
|
|
1638
|
-
* Local CLI status retrieval
|
|
1639
|
-
*/
|
|
1640
|
-
async localGetCliStatus(args) {
|
|
1641
|
-
console.error(`[Stdio Wrapper] Local CLI status retrieval`);
|
|
1434
|
+
// Map known IDE client names to their corresponding CLI IDs
|
|
1435
|
+
// These are the clientInfo.name values sent during MCP initialize handshake
|
|
1436
|
+
const ideToCliMap = {
|
|
1437
|
+
// Claude Code variants
|
|
1438
|
+
'claude-code': 'claude_code',
|
|
1439
|
+
'claude_code': 'claude_code',
|
|
1440
|
+
'claude code': 'claude_code',
|
|
1441
|
+
'claudecode': 'claude_code',
|
|
1442
|
+
// Cursor (uses Claude under the hood but is a separate IDE)
|
|
1443
|
+
// Don't exclude any CLI for Cursor since it's not calling itself
|
|
1444
|
+
// Gemini CLI / Google IDX
|
|
1445
|
+
'gemini-cli': 'gemini_cli',
|
|
1446
|
+
'gemini_cli': 'gemini_cli',
|
|
1447
|
+
// Codex CLI / OpenAI
|
|
1448
|
+
'codex-cli': 'codex_cli',
|
|
1449
|
+
'codex_cli': 'codex_cli',
|
|
1450
|
+
'codex': 'codex_cli',
|
|
1451
|
+
};
|
|
1642
1452
|
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
if (providerId) {
|
|
1648
|
-
// Get specific provider status
|
|
1649
|
-
const status = await this.cliManager.getCliStatus(providerId);
|
|
1650
|
-
results = status;
|
|
1651
|
-
} else {
|
|
1652
|
-
// Get all providers status
|
|
1653
|
-
const providers = this.cliManager.getProviders();
|
|
1654
|
-
for (const provider of providers) {
|
|
1655
|
-
const status = await this.cliManager.getCliStatus(provider.id);
|
|
1656
|
-
results[provider.id] = status;
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
// Update database with current status
|
|
1661
|
-
await this.updateCliStatusInDatabase(results);
|
|
1662
|
-
|
|
1663
|
-
return {
|
|
1664
|
-
success: true,
|
|
1665
|
-
results,
|
|
1666
|
-
message: 'Local CLI status retrieved successfully',
|
|
1667
|
-
timestamp: new Date().toISOString(),
|
|
1668
|
-
local_only: true
|
|
1669
|
-
};
|
|
1670
|
-
|
|
1671
|
-
} catch (error) {
|
|
1672
|
-
console.error('[Stdio Wrapper] Local CLI status error:', error);
|
|
1673
|
-
return {
|
|
1674
|
-
success: false,
|
|
1675
|
-
error: error.message,
|
|
1676
|
-
timestamp: new Date().toISOString(),
|
|
1677
|
-
local_only: true
|
|
1678
|
-
};
|
|
1453
|
+
// Direct match first
|
|
1454
|
+
if (ideToCliMap[clientName]) {
|
|
1455
|
+
return ideToCliMap[clientName];
|
|
1679
1456
|
}
|
|
1457
|
+
|
|
1458
|
+
// Fuzzy match: check if client name contains known patterns
|
|
1459
|
+
if (clientName.includes('claude')) return 'claude_code';
|
|
1460
|
+
if (clientName.includes('gemini')) return 'gemini_cli';
|
|
1461
|
+
if (clientName.includes('codex')) return 'codex_cli';
|
|
1462
|
+
|
|
1463
|
+
return null;
|
|
1680
1464
|
}
|
|
1681
1465
|
|
|
1682
1466
|
/**
|
|
@@ -1765,14 +1549,26 @@ Error: ${error.message}`
|
|
|
1765
1549
|
// CLI priority order: Claude Code first, then Gemini, then Codex
|
|
1766
1550
|
const cliPriorityOrder = ['claude_code', 'gemini_cli', 'codex_cli'];
|
|
1767
1551
|
|
|
1552
|
+
// Detect if we should exclude the current IDE's CLI to avoid recursive calls
|
|
1553
|
+
const excludedCli = this.getExcludedCliForCurrentIDE();
|
|
1554
|
+
if (excludedCli) {
|
|
1555
|
+
console.error(`[Stdio Wrapper] Excluding CLI '${excludedCli}' (current IDE: ${this.clientInfo?.name}) to avoid recursive calls`);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1768
1558
|
// Build merged provider list: CLIs first, then API-only
|
|
1769
1559
|
const finalProviders = [];
|
|
1770
1560
|
const usedProviderNames = new Set();
|
|
1771
1561
|
|
|
1772
|
-
// STEP 1: Add ALL available CLIs
|
|
1562
|
+
// STEP 1: Add ALL available CLIs (in priority order) - they're FREE
|
|
1563
|
+
// Don't limit to maxPerspectives here — we run all CLIs in parallel
|
|
1564
|
+
// and take the first maxPerspectives successes (fast-collect pattern)
|
|
1565
|
+
// Skip the CLI that matches the current IDE to avoid recursive calls
|
|
1773
1566
|
for (const cliId of cliPriorityOrder) {
|
|
1774
|
-
if (finalProviders.length >= maxPerspectives) break;
|
|
1775
1567
|
if (!availableClis.includes(cliId)) continue;
|
|
1568
|
+
if (cliId === excludedCli) {
|
|
1569
|
+
console.error(`[Stdio Wrapper] [CLI-FIRST] Skipping ${cliId} (same as current IDE — would cause recursive call)`);
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1776
1572
|
|
|
1777
1573
|
const providerName = cliToProviderMap[cliId];
|
|
1778
1574
|
usedProviderNames.add(providerName);
|
|
@@ -1788,7 +1584,7 @@ Error: ${error.message}`
|
|
|
1788
1584
|
|
|
1789
1585
|
finalProviders.push({
|
|
1790
1586
|
provider: providerName,
|
|
1791
|
-
model: configuredProvider?.model || null, // null = use CLI
|
|
1587
|
+
model: configuredProvider?.model || null, // null = use CLI default model
|
|
1792
1588
|
cliId: cliId,
|
|
1793
1589
|
source: 'cli',
|
|
1794
1590
|
tier: null // CLIs don't use credits tiers
|
|
@@ -1817,7 +1613,7 @@ Error: ${error.message}`
|
|
|
1817
1613
|
console.error(`[Stdio Wrapper] [CLI-FIRST] Added API/credits: ${normalizedProvider} (${p.model})${p.tier ? ` [${p.tier}]` : ''}`);
|
|
1818
1614
|
}
|
|
1819
1615
|
|
|
1820
|
-
console.error(`[Stdio Wrapper] Final provider list (${finalProviders.length}
|
|
1616
|
+
console.error(`[Stdio Wrapper] Final provider list (${finalProviders.length}, need ${maxPerspectives}): ${finalProviders.map(p => `${p.provider}[${p.source}]`).join(', ')}`);
|
|
1821
1617
|
|
|
1822
1618
|
// Separate into CLI entries (local execution) vs API entries (remote execution)
|
|
1823
1619
|
const cliProviderEntries = finalProviders.filter(p => p.source === 'cli');
|
|
@@ -1825,7 +1621,8 @@ Error: ${error.message}`
|
|
|
1825
1621
|
|
|
1826
1622
|
console.error(`[Stdio Wrapper] Provider breakdown: CLI=${cliProviderEntries.map(p => p.cliId).join(', ') || 'none'}, API-only=${apiOnlyProviders.map(p => p.provider).join(', ') || 'none'}`);
|
|
1827
1623
|
|
|
1828
|
-
// Run
|
|
1624
|
+
// Run ALL CLI prompts concurrently with fast-collect pattern
|
|
1625
|
+
// Resolves once we have maxPerspectives successes (don't wait for slow CLIs)
|
|
1829
1626
|
if (cliProviderEntries.length > 0) {
|
|
1830
1627
|
const cliPromises = cliProviderEntries.map(async (providerEntry) => {
|
|
1831
1628
|
try {
|
|
@@ -1866,7 +1663,10 @@ Error: ${error.message}`
|
|
|
1866
1663
|
};
|
|
1867
1664
|
}
|
|
1868
1665
|
});
|
|
1869
|
-
|
|
1666
|
+
|
|
1667
|
+
// Fast-collect: resolve early once we have maxPerspectives successes OR all complete
|
|
1668
|
+
localResults = await this.collectFirstNSuccesses(cliPromises, maxPerspectives);
|
|
1669
|
+
console.error(`[Stdio Wrapper] Fast-collect: got ${localResults.filter(r => r.success).length} successful, ${localResults.filter(r => !r.success).length} failed out of ${cliPromises.length} CLIs`);
|
|
1870
1670
|
}
|
|
1871
1671
|
|
|
1872
1672
|
// Store API-only providers info for remote API call
|
|
@@ -1941,6 +1741,42 @@ Error: ${error.message}`
|
|
|
1941
1741
|
}
|
|
1942
1742
|
}
|
|
1943
1743
|
|
|
1744
|
+
/**
|
|
1745
|
+
* Collect results from parallel promises, resolving early once we have N successes
|
|
1746
|
+
* This avoids waiting for slow/timed-out CLIs when we already have enough results
|
|
1747
|
+
* @param {Promise[]} promises - Array of promises to collect from
|
|
1748
|
+
* @param {number} needed - Number of successful results needed
|
|
1749
|
+
* @returns {Promise<Array>} Array of results (may include failures if not enough successes)
|
|
1750
|
+
*/
|
|
1751
|
+
collectFirstNSuccesses(promises, needed) {
|
|
1752
|
+
return new Promise((resolve) => {
|
|
1753
|
+
const results = [];
|
|
1754
|
+
let successCount = 0;
|
|
1755
|
+
let completedCount = 0;
|
|
1756
|
+
let resolved = false;
|
|
1757
|
+
|
|
1758
|
+
if (promises.length === 0) {
|
|
1759
|
+
resolve([]);
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
for (const promise of promises) {
|
|
1764
|
+
promise.then(result => {
|
|
1765
|
+
if (resolved) return;
|
|
1766
|
+
results.push(result);
|
|
1767
|
+
if (result.success) successCount++;
|
|
1768
|
+
completedCount++;
|
|
1769
|
+
|
|
1770
|
+
// Resolve early if we have enough successes OR all promises done
|
|
1771
|
+
if (successCount >= needed || completedCount >= promises.length) {
|
|
1772
|
+
resolved = true;
|
|
1773
|
+
resolve([...results]); // Copy to prevent mutation from late arrivals
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1944
1780
|
/**
|
|
1945
1781
|
* Get all available and authenticated CLI providers
|
|
1946
1782
|
* Uses user's provider order from dashboard (display_order) instead of hardcoded order
|
|
@@ -2278,54 +2114,6 @@ Error: ${error.message}`
|
|
|
2278
2114
|
combineAllCliAndPerspectives(localResults, perspectivesResult, args) {
|
|
2279
2115
|
// Ensure perspectivesResult is always an object to prevent undefined errors
|
|
2280
2116
|
const safePersp = perspectivesResult || { success: false, error: 'No response from perspectives server' };
|
|
2281
|
-
|
|
2282
|
-
const combinedResult = {
|
|
2283
|
-
success: true,
|
|
2284
|
-
timestamp: new Date().toISOString(),
|
|
2285
|
-
mode: args.mode,
|
|
2286
|
-
local_cli_count: localResults.length,
|
|
2287
|
-
sections: {
|
|
2288
|
-
local: localResults,
|
|
2289
|
-
remote: safePersp
|
|
2290
|
-
}
|
|
2291
|
-
};
|
|
2292
|
-
|
|
2293
|
-
// Check if any local CLIs succeeded
|
|
2294
|
-
const successfulClis = localResults.filter(result => result.success);
|
|
2295
|
-
const hasSomeLocalSuccess = successfulClis.length > 0;
|
|
2296
|
-
|
|
2297
|
-
// Determine overall success and content
|
|
2298
|
-
if (hasSomeLocalSuccess && safePersp.success) {
|
|
2299
|
-
combinedResult.content = this.formatMultipleCliResponse(localResults, safePersp, false);
|
|
2300
|
-
combinedResult.tokens_used = successfulClis.reduce((total, cli) => total + (cli.tokens_used || 0), 0);
|
|
2301
|
-
combinedResult.latency_ms = Math.max(...successfulClis.map(cli => cli.latency_ms || 0));
|
|
2302
|
-
} else if (!hasSomeLocalSuccess && safePersp.success) {
|
|
2303
|
-
// Complete fallback case - no local CLIs worked
|
|
2304
|
-
combinedResult.content = this.formatMultipleCliResponse(localResults, safePersp, true);
|
|
2305
|
-
combinedResult.fallback_used = true;
|
|
2306
|
-
combinedResult.tokens_used = 0; // No local tokens used
|
|
2307
|
-
} else if (hasSomeLocalSuccess && !safePersp.success) {
|
|
2308
|
-
// Local CLIs succeeded, remote failed
|
|
2309
|
-
combinedResult.content = this.formatMultipleCliResponse(localResults, safePersp, false);
|
|
2310
|
-
combinedResult.tokens_used = successfulClis.reduce((total, cli) => total + (cli.tokens_used || 0), 0);
|
|
2311
|
-
combinedResult.latency_ms = Math.max(...successfulClis.map(cli => cli.latency_ms || 0));
|
|
2312
|
-
} else {
|
|
2313
|
-
// Both failed
|
|
2314
|
-
combinedResult.success = false;
|
|
2315
|
-
const cliErrors = localResults.map(cli => `${cli.provider_id}: ${cli.error || 'Unknown error'}`).join('; ');
|
|
2316
|
-
const perspectivesError = safePersp.error || 'Unknown remote error';
|
|
2317
|
-
combinedResult.error = `All local CLIs failed: ${cliErrors}; Perspectives also failed: ${perspectivesError}`;
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
|
-
return combinedResult;
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2323
|
-
/**
|
|
2324
|
-
* Format multiple CLI responses with remote perspectives
|
|
2325
|
-
*/
|
|
2326
|
-
formatMultipleCliResponse(localResults, perspectivesResult, isFallback) {
|
|
2327
|
-
// Safety check - ensure perspectivesResult is always an object
|
|
2328
|
-
const safePersp = perspectivesResult || { success: false, error: 'No perspectives data' };
|
|
2329
2117
|
let formatted = '';
|
|
2330
2118
|
|
|
2331
2119
|
// Show all local CLI responses
|
|
@@ -2668,7 +2456,7 @@ Error: ${error.message}`
|
|
|
2668
2456
|
/**
|
|
2669
2457
|
* Fetch user's model preferences from API keys
|
|
2670
2458
|
* Returns a map of CLI provider -> default_model
|
|
2671
|
-
* Also fetches and caches
|
|
2459
|
+
* Also fetches and caches perspectives_per_message setting and allProviders list
|
|
2672
2460
|
*/
|
|
2673
2461
|
async fetchUserModelPreferences() {
|
|
2674
2462
|
// Check cache first
|