polydev-ai 1.9.16 → 1.9.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/mcp/stdio-wrapper.js +284 -17
  2. package/package.json +1 -1
@@ -299,11 +299,11 @@ class StdioMCPWrapper {
299
299
 
300
300
  // Pending session file for surviving restarts
301
301
  this.PENDING_SESSION_FILE = path.join(os.homedir(), '.polydev-pending-session');
302
-
302
+
303
303
  // MCP client info (set during initialize handshake)
304
304
  // Used to detect which IDE is running us and exclude its CLI to avoid recursive calls
305
305
  this.clientInfo = null;
306
-
306
+
307
307
  // Initialize CLI Manager for local CLI functionality
308
308
  // Disable StatusReporter - it's redundant (updateCliStatusInDatabase handles DB updates via /api/cli-status-update)
309
309
  // and causes 401 errors because /api/mcp uses different auth than /api/cli-status-update
@@ -1000,10 +1000,11 @@ Token will be saved automatically after login.`
1000
1000
  perspectivesSection += ` (${perspectiveCount} active)`;
1001
1001
 
1002
1002
  // Show providers in order with their selected models
1003
- for (const p of modelPrefs.allProviders) {
1003
+ for (let i = 0; i < modelPrefs.allProviders.length; i++) {
1004
+ const p = modelPrefs.allProviders[i];
1004
1005
  const providerName = p.provider.charAt(0).toUpperCase() + p.provider.slice(1);
1005
1006
  const source = p.cliId ? 'CLI' : (p.tier ? `Credits/${p.tier}` : 'API');
1006
- perspectivesSection += `\n ${providerName.padEnd(12)} ${p.model} [${source}]`;
1007
+ perspectivesSection += `\n ${i + 1}. ${providerName.padEnd(12)} ${p.model} [${source}]`;
1007
1008
  }
1008
1009
  } else {
1009
1010
  perspectivesSection += '\n (none configured - using defaults)';
@@ -1421,6 +1422,273 @@ Error: ${error.message}`
1421
1422
  </html>`;
1422
1423
  }
1423
1424
 
1425
+ async forwardToRemoteServer(request) {
1426
+ console.error(`[Stdio Wrapper] Forwarding request to remote server`);
1427
+
1428
+ try {
1429
+ // Use AbortController for timeout if available, otherwise rely on fetch timeout
1430
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
1431
+ const timeoutId = controller ? setTimeout(() => controller.abort(), 240000) : null; // 240s timeout
1432
+
1433
+ const response = await fetch('https://www.polydev.ai/api/mcp', {
1434
+ method: 'POST',
1435
+ headers: {
1436
+ 'Content-Type': 'application/json',
1437
+ 'Authorization': `Bearer ${this.userToken}`,
1438
+ 'User-Agent': 'polydev-stdio-wrapper/1.0.0'
1439
+ },
1440
+ body: JSON.stringify(request),
1441
+ ...(controller ? { signal: controller.signal } : {})
1442
+ });
1443
+
1444
+ if (timeoutId) clearTimeout(timeoutId);
1445
+
1446
+ if (!response.ok) {
1447
+ const errorText = await response.text();
1448
+ console.error(`[Stdio Wrapper] Remote server error: ${response.status} - ${errorText}`);
1449
+
1450
+ // Handle 401 specifically - token expired/invalid, trigger re-auth
1451
+ if (response.status === 401) {
1452
+ console.error('[Polydev] Remote API returned 401, auto-triggering re-authentication...');
1453
+ return await this.triggerReAuth(request.id, 'Authentication expired. Re-authenticating...');
1454
+ }
1455
+
1456
+ return {
1457
+ jsonrpc: '2.0',
1458
+ id: request.id,
1459
+ error: {
1460
+ code: -32603,
1461
+ message: `Remote server error: ${response.status} - ${errorText}`
1462
+ }
1463
+ };
1464
+ }
1465
+
1466
+ const result = await response.json();
1467
+ console.error(`[Stdio Wrapper] Got response from remote server`);
1468
+ return result;
1469
+ } catch (error) {
1470
+ console.error(`[Stdio Wrapper] Network error:`, error.message);
1471
+ return {
1472
+ jsonrpc: '2.0',
1473
+ id: request.id,
1474
+ error: {
1475
+ code: -32603,
1476
+ message: `Network error: ${error.message}`
1477
+ }
1478
+ };
1479
+ }
1480
+ }
1481
+
1482
+ /**
1483
+ * Check if a tool is a CLI tool that should be handled locally
1484
+ */
1485
+ isCliTool(toolName) {
1486
+ const cliTools = [
1487
+ 'force_cli_detection',
1488
+ 'get_cli_status',
1489
+ 'send_cli_prompt',
1490
+ 'polydev.force_cli_detection',
1491
+ 'polydev.get_cli_status',
1492
+ 'polydev.send_cli_prompt'
1493
+ ];
1494
+ return cliTools.includes(toolName);
1495
+ }
1496
+
1497
+ /**
1498
+ * Handle get_perspectives with local CLIs + remote perspectives
1499
+ */
1500
+ async handleGetPerspectivesWithCLIs(params, id) {
1501
+ console.error(`[Stdio Wrapper] Handling get_perspectives with local CLIs + remote`);
1502
+
1503
+ try {
1504
+ // Use existing localSendCliPrompt logic which already:
1505
+ // 1. Checks all local CLIs
1506
+ // 2. Calls remote perspectives
1507
+ // 3. Combines results
1508
+ const result = await this.localSendCliPrompt(params.arguments);
1509
+
1510
+ // Check if result indicates auth failure (from forwardToRemoteServer 401 handling)
1511
+ const resultText = result.content || this.formatCliResponse(result);
1512
+ if (result.result?.content?.[0]?.text?.includes('RE-AUTHENTICATION REQUIRED')) {
1513
+ // Auth failure already handled with re-auth response - pass through
1514
+ return result;
1515
+ }
1516
+
1517
+ return {
1518
+ jsonrpc: '2.0',
1519
+ id,
1520
+ result: {
1521
+ content: [
1522
+ {
1523
+ type: 'text',
1524
+ text: resultText
1525
+ }
1526
+ ]
1527
+ }
1528
+ };
1529
+
1530
+ } catch (error) {
1531
+ console.error(`[Stdio Wrapper] get_perspectives error:`, error);
1532
+
1533
+ // Check if error is auth-related
1534
+ const errMsg = (error.message || '').toLowerCase();
1535
+ if (errMsg.includes('401') || errMsg.includes('unauthorized') || errMsg.includes('auth') || errMsg.includes('token')) {
1536
+ console.error('[Polydev] Perspectives auth error, auto-triggering re-authentication...');
1537
+ return await this.triggerReAuth(id, 'Authentication expired. Re-authenticating...');
1538
+ }
1539
+
1540
+ return {
1541
+ jsonrpc: '2.0',
1542
+ id,
1543
+ error: {
1544
+ code: -32603,
1545
+ message: error.message
1546
+ }
1547
+ };
1548
+ }
1549
+ }
1550
+
1551
+ /**
1552
+ * Handle CLI tools locally without remote server calls
1553
+ */
1554
+ async handleLocalCliTool(request) {
1555
+ const { method, params, id } = request;
1556
+ const { name: toolName, arguments: args } = params;
1557
+
1558
+ console.error(`[Stdio Wrapper] Handling local CLI tool: ${toolName}`);
1559
+
1560
+ try {
1561
+ let result;
1562
+
1563
+ switch (toolName) {
1564
+ case 'force_cli_detection':
1565
+ case 'polydev.force_cli_detection':
1566
+ result = await this.localForceCliDetection(args);
1567
+ break;
1568
+
1569
+ case 'get_cli_status':
1570
+ case 'polydev.get_cli_status':
1571
+ result = await this.localGetCliStatus(args);
1572
+ break;
1573
+
1574
+ case 'send_cli_prompt':
1575
+ case 'polydev.send_cli_prompt':
1576
+ result = await this.localSendCliPrompt(args);
1577
+ break;
1578
+
1579
+ default:
1580
+ throw new Error(`Unknown CLI tool: ${toolName}`);
1581
+ }
1582
+
1583
+ return {
1584
+ jsonrpc: '2.0',
1585
+ id,
1586
+ result: {
1587
+ content: [
1588
+ {
1589
+ type: 'text',
1590
+ text: this.formatCliResponse(result)
1591
+ }
1592
+ ]
1593
+ }
1594
+ };
1595
+
1596
+ } catch (error) {
1597
+ console.error(`[Stdio Wrapper] CLI tool error:`, error);
1598
+ return {
1599
+ jsonrpc: '2.0',
1600
+ id,
1601
+ error: {
1602
+ code: -32603,
1603
+ message: error.message
1604
+ }
1605
+ };
1606
+ }
1607
+ }
1608
+
1609
+ /**
1610
+ * Local CLI detection implementation with database updates
1611
+ */
1612
+ async localForceCliDetection(args) {
1613
+ console.error(`[Stdio Wrapper] Local CLI detection with model detection started`);
1614
+
1615
+ try {
1616
+ const providerId = args.provider_id; // Optional - detect specific provider
1617
+
1618
+ // Force detection using CLI Manager (no remote API calls)
1619
+ const results = await this.cliManager.forceCliDetection(providerId);
1620
+ console.error(`[Stdio Wrapper] CLI detection results:`, JSON.stringify(results, null, 2));
1621
+
1622
+ // Save status locally to file-based cache
1623
+ await this.saveLocalCliStatus(results);
1624
+
1625
+ // Update database with CLI status
1626
+ await this.updateCliStatusInDatabase(results);
1627
+
1628
+ return {
1629
+ success: true,
1630
+ results,
1631
+ message: `Local CLI detection completed for ${providerId || 'all providers'}`,
1632
+ timestamp: new Date().toISOString(),
1633
+ local_only: true
1634
+ };
1635
+
1636
+ } catch (error) {
1637
+ console.error('[Stdio Wrapper] Local CLI detection error:', error);
1638
+ return {
1639
+ success: false,
1640
+ error: error.message,
1641
+ timestamp: new Date().toISOString(),
1642
+ local_only: true
1643
+ };
1644
+ }
1645
+ }
1646
+
1647
+ /**
1648
+ * Local CLI status retrieval
1649
+ */
1650
+ async localGetCliStatus(args) {
1651
+ console.error(`[Stdio Wrapper] Local CLI status retrieval`);
1652
+
1653
+ try {
1654
+ const providerId = args.provider_id;
1655
+ let results = {};
1656
+
1657
+ if (providerId) {
1658
+ // Get specific provider status
1659
+ const status = await this.cliManager.getCliStatus(providerId);
1660
+ results = status;
1661
+ } else {
1662
+ // Get all providers status
1663
+ const providers = this.cliManager.getProviders();
1664
+ for (const provider of providers) {
1665
+ const status = await this.cliManager.getCliStatus(provider.id);
1666
+ results[provider.id] = status;
1667
+ }
1668
+ }
1669
+
1670
+ // Update database with current status
1671
+ await this.updateCliStatusInDatabase(results);
1672
+
1673
+ return {
1674
+ success: true,
1675
+ results,
1676
+ message: 'Local CLI status retrieved successfully',
1677
+ timestamp: new Date().toISOString(),
1678
+ local_only: true
1679
+ };
1680
+
1681
+ } catch (error) {
1682
+ console.error('[Stdio Wrapper] Local CLI status error:', error);
1683
+ return {
1684
+ success: false,
1685
+ error: error.message,
1686
+ timestamp: new Date().toISOString(),
1687
+ local_only: true
1688
+ };
1689
+ }
1690
+ }
1691
+
1424
1692
  /**
1425
1693
  * Get the CLI ID to exclude based on the current IDE client.
1426
1694
  * Prevents recursive calls (e.g., Claude Code calling claude_code CLI which spawns another Claude Code).
@@ -1428,9 +1696,9 @@ Error: ${error.message}`
1428
1696
  */
1429
1697
  getExcludedCliForCurrentIDE() {
1430
1698
  if (!this.clientInfo?.name) return null;
1431
-
1699
+
1432
1700
  const clientName = this.clientInfo.name.toLowerCase();
1433
-
1701
+
1434
1702
  // Map known IDE client names to their corresponding CLI IDs
1435
1703
  // These are the clientInfo.name values sent during MCP initialize handshake
1436
1704
  const ideToCliMap = {
@@ -1439,8 +1707,7 @@ Error: ${error.message}`
1439
1707
  'claude_code': 'claude_code',
1440
1708
  'claude code': 'claude_code',
1441
1709
  '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
1710
+ // Cursor uses Claude under the hood but is a separate IDE — don't exclude
1444
1711
  // Gemini CLI / Google IDX
1445
1712
  'gemini-cli': 'gemini_cli',
1446
1713
  'gemini_cli': 'gemini_cli',
@@ -1449,17 +1716,17 @@ Error: ${error.message}`
1449
1716
  'codex_cli': 'codex_cli',
1450
1717
  'codex': 'codex_cli',
1451
1718
  };
1452
-
1719
+
1453
1720
  // Direct match first
1454
1721
  if (ideToCliMap[clientName]) {
1455
1722
  return ideToCliMap[clientName];
1456
1723
  }
1457
-
1724
+
1458
1725
  // Fuzzy match: check if client name contains known patterns
1459
1726
  if (clientName.includes('claude')) return 'claude_code';
1460
1727
  if (clientName.includes('gemini')) return 'gemini_cli';
1461
1728
  if (clientName.includes('codex')) return 'codex_cli';
1462
-
1729
+
1463
1730
  return null;
1464
1731
  }
1465
1732
 
@@ -1548,17 +1815,17 @@ Error: ${error.message}`
1548
1815
 
1549
1816
  // CLI priority order: Claude Code first, then Gemini, then Codex
1550
1817
  const cliPriorityOrder = ['claude_code', 'gemini_cli', 'codex_cli'];
1551
-
1818
+
1552
1819
  // Detect if we should exclude the current IDE's CLI to avoid recursive calls
1553
1820
  const excludedCli = this.getExcludedCliForCurrentIDE();
1554
1821
  if (excludedCli) {
1555
1822
  console.error(`[Stdio Wrapper] Excluding CLI '${excludedCli}' (current IDE: ${this.clientInfo?.name}) to avoid recursive calls`);
1556
1823
  }
1557
-
1824
+
1558
1825
  // Build merged provider list: CLIs first, then API-only
1559
1826
  const finalProviders = [];
1560
1827
  const usedProviderNames = new Set();
1561
-
1828
+
1562
1829
  // STEP 1: Add ALL available CLIs (in priority order) - they're FREE
1563
1830
  // Don't limit to maxPerspectives here — we run all CLIs in parallel
1564
1831
  // and take the first maxPerspectives successes (fast-collect pattern)
@@ -1584,7 +1851,7 @@ Error: ${error.message}`
1584
1851
 
1585
1852
  finalProviders.push({
1586
1853
  provider: providerName,
1587
- model: configuredProvider?.model || null, // null = use CLI default model
1854
+ model: configuredProvider?.model || null, // null = use CLI's default model
1588
1855
  cliId: cliId,
1589
1856
  source: 'cli',
1590
1857
  tier: null // CLIs don't use credits tiers
@@ -1664,7 +1931,7 @@ Error: ${error.message}`
1664
1931
  }
1665
1932
  });
1666
1933
 
1667
- // Fast-collect: resolve early once we have maxPerspectives successes OR all complete
1934
+ // Fast-collect: resolve once we have maxPerspectives successes OR all complete
1668
1935
  localResults = await this.collectFirstNSuccesses(cliPromises, maxPerspectives);
1669
1936
  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`);
1670
1937
  }
@@ -2456,7 +2723,7 @@ Error: ${error.message}`
2456
2723
  /**
2457
2724
  * Fetch user's model preferences from API keys
2458
2725
  * Returns a map of CLI provider -> default_model
2459
- * Also fetches and caches perspectives_per_message setting and allProviders list
2726
+ * Also fetches and caches perspectivesPerMessage setting and allProviders list
2460
2727
  */
2461
2728
  async fetchUserModelPreferences() {
2462
2729
  // Check cache first
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-ai",
3
- "version": "1.9.16",
3
+ "version": "1.9.17",
4
4
  "engines": {
5
5
  "node": ">=20.x <=22.x"
6
6
  },