polydev-ai 1.9.26 → 1.9.27

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.
@@ -1566,76 +1566,64 @@ To re-login: /polydev:login`
1566
1566
  </html>`;
1567
1567
  }
1568
1568
 
1569
- async forwardToRemoteServer(request) {
1570
- console.error(`[Stdio Wrapper] Forwarding request to remote server`);
1571
-
1572
- try {
1573
- // Use AbortController for timeout if available, otherwise rely on fetch timeout
1574
- const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
1575
- const timeoutId = controller ? setTimeout(() => controller.abort(), 400000) : null; // 400s timeout
1576
-
1577
- const response = await fetch('https://www.polydev.ai/api/mcp', {
1578
- method: 'POST',
1579
- headers: {
1580
- 'Content-Type': 'application/json',
1581
- 'Authorization': `Bearer ${this.userToken}`,
1582
- 'User-Agent': 'polydev-stdio-wrapper/1.0.0'
1583
- },
1584
- body: JSON.stringify(request),
1585
- ...(controller ? { signal: controller.signal } : {})
1586
- });
1587
-
1588
- if (timeoutId) clearTimeout(timeoutId);
1589
-
1590
- if (!response.ok) {
1591
- const errorText = await response.text();
1592
- console.error(`[Stdio Wrapper] Remote server error: ${response.status} - ${errorText}`);
1593
-
1594
- // Handle 401 specifically - token expired/invalid, trigger re-auth
1595
- if (response.status === 401) {
1596
- console.error('[Polydev] Remote API returned 401, auto-triggering re-authentication...');
1597
- return await this.triggerReAuth(request.id, 'Authentication expired. Re-authenticating...');
1598
- }
1599
-
1600
- return {
1601
- jsonrpc: '2.0',
1602
- id: request.id,
1603
- error: {
1604
- code: -32603,
1605
- message: `Remote server error: ${response.status} - ${errorText}`
1606
- }
1607
- };
1569
+ /**
1570
+ * Send an MCP progress notification to the client
1571
+ * This is a JSON-RPC notification (no id) that compatible clients use to:
1572
+ * 1. Display progress UI (status bar, spinners)
1573
+ * 2. Reset their tool call timeout (if resetTimeoutOnProgress is enabled)
1574
+ * @param {string|number} progressToken - Token from request's _meta.progressToken
1575
+ * @param {number} progress - Current progress value (must increase monotonically)
1576
+ * @param {number} total - Total expected progress value
1577
+ * @param {string} message - Human-readable status message
1578
+ */
1579
+ sendProgressNotification(progressToken, progress, total, message) {
1580
+ if (!progressToken) return; // Client didn't request progress
1581
+
1582
+ const notification = {
1583
+ jsonrpc: '2.0',
1584
+ method: 'notifications/progress',
1585
+ params: {
1586
+ progressToken,
1587
+ progress,
1588
+ total,
1589
+ message
1608
1590
  }
1609
-
1610
- const result = await response.json();
1611
- console.error(`[Stdio Wrapper] Got response from remote server`);
1612
- return result;
1613
- } catch (error) {
1614
- console.error(`[Stdio Wrapper] Network error:`, error.message);
1615
- return {
1616
- jsonrpc: '2.0',
1617
- id: request.id,
1618
- error: {
1619
- code: -32603,
1620
- message: `Network error: ${error.message}`
1621
- }
1622
- };
1591
+ };
1592
+
1593
+ try {
1594
+ process.stdout.write(JSON.stringify(notification) + '\n');
1595
+ console.error(`[Polydev] Progress: ${progress}/${total} - ${message}`);
1596
+ } catch (err) {
1597
+ console.error(`[Polydev] Failed to send progress notification:`, err.message);
1623
1598
  }
1624
1599
  }
1625
1600
 
1626
1601
  /**
1627
- * Check if a tool is a CLI tool that should be handled locally
1602
+ * Start a periodic progress heartbeat to prevent client-side timeouts.
1603
+ * Sends a progress notification every intervalMs to signal the server is still working.
1604
+ * Compatible MCP clients will reset their timeout clock on each notification.
1605
+ * @param {string|number} progressToken - Token from request's _meta.progressToken
1606
+ * @param {number} intervalMs - Heartbeat interval in milliseconds (default: 10s)
1607
+ * @returns {{ stop: Function, tick: Function }} Controller to stop heartbeat and manually tick progress
1628
1608
  */
1629
- isCliTool(toolName) {
1630
- const cliTools = [
1631
- 'force_cli_detection',
1632
- 'get_cli_status',
1633
- 'send_cli_prompt',
1634
- 'polydev.force_cli_detection',
1635
- 'polydev.get_cli_status',
1636
- 'polydev.send_cli_prompt'
1637
- ];
1638
- return cliTools.includes(toolName);
1609
+ startProgressHeartbeat(progressToken, intervalMs = 10000) {
1610
+ if (!progressToken) return { stop: () => {}, tick: () => {} };
1611
+
1612
+ let currentProgress = 0;
1613
+ const total = 100; // Use percentage-style progress
1614
+
1615
+ const interval = setInterval(() => {
1616
+ currentProgress = Math.min(currentProgress + 5, 95); // Never reach 100 until done
1617
+ this.sendProgressNotification(progressToken, currentProgress, total, 'Processing perspectives...');
1618
+ }, intervalMs);
1619
+
1620
+ return {
1621
+ stop: () => clearInterval(interval),
1622
+ tick: (progress, message) => {
1623
+ currentProgress = progress;
1624
+ this.sendProgressNotification(progressToken, progress, total, message);
1625
+ }
1626
+ };
1639
1627
  }
1640
1628
 
1641
1629
  /**
@@ -1644,12 +1632,30 @@ To re-login: /polydev:login`
1644
1632
  async handleGetPerspectivesWithCLIs(params, id) {
1645
1633
  console.error(`[Stdio Wrapper] Handling get_perspectives with local CLIs + remote`);
1646
1634
 
1635
+ // Extract progress token from request metadata (MCP spec)
1636
+ // Clients that want progress updates include this in _meta.progressToken
1637
+ const progressToken = params?._meta?.progressToken;
1638
+ if (progressToken) {
1639
+ console.error(`[Stdio Wrapper] Client requested progress updates (token: ${progressToken})`);
1640
+ }
1641
+
1642
+ // Start periodic heartbeat to prevent client-side timeouts
1643
+ // Sends progress notification every 10s to keep the connection alive
1644
+ const heartbeat = this.startProgressHeartbeat(progressToken);
1645
+
1647
1646
  try {
1647
+ // Send initial progress
1648
+ heartbeat.tick(5, 'Initializing perspectives engine...');
1649
+
1648
1650
  // Use existing localSendCliPrompt logic which already:
1649
1651
  // 1. Checks all local CLIs
1650
1652
  // 2. Calls remote perspectives
1651
1653
  // 3. Combines results
1652
- const result = await this.localSendCliPrompt(params.arguments);
1654
+ const result = await this.localSendCliPrompt(params.arguments, progressToken);
1655
+
1656
+ // Stop heartbeat before sending final response
1657
+ heartbeat.stop();
1658
+ heartbeat.tick(100, 'Complete');
1653
1659
 
1654
1660
  // Check if result indicates auth failure (from forwardToRemoteServer 401 handling)
1655
1661
  if (result?.result?.content?.[0]?.text?.includes('RE-AUTHENTICATION REQUIRED')) {
@@ -1678,6 +1684,7 @@ To re-login: /polydev:login`
1678
1684
  };
1679
1685
 
1680
1686
  } catch (error) {
1687
+ heartbeat.stop(); // Always clean up heartbeat
1681
1688
  console.error(`[Stdio Wrapper] get_perspectives error:`, error);
1682
1689
 
1683
1690
  // Check if error is auth-related
@@ -1704,9 +1711,13 @@ To re-login: /polydev:login`
1704
1711
  async handleLocalCliTool(request) {
1705
1712
  const { method, params, id } = request;
1706
1713
  const { name: toolName, arguments: args } = params;
1714
+ const progressToken = params?._meta?.progressToken;
1707
1715
 
1708
1716
  console.error(`[Stdio Wrapper] Handling local CLI tool: ${toolName}`);
1709
1717
 
1718
+ // Start heartbeat for long-running CLI operations
1719
+ const heartbeat = this.startProgressHeartbeat(progressToken);
1720
+
1710
1721
  try {
1711
1722
  let result;
1712
1723
 
@@ -1723,13 +1734,14 @@ To re-login: /polydev:login`
1723
1734
 
1724
1735
  case 'send_cli_prompt':
1725
1736
  case 'polydev.send_cli_prompt':
1726
- result = await this.localSendCliPrompt(args);
1737
+ result = await this.localSendCliPrompt(args, progressToken);
1727
1738
  break;
1728
1739
 
1729
1740
  default:
1730
1741
  throw new Error(`Unknown CLI tool: ${toolName}`);
1731
1742
  }
1732
1743
 
1744
+ heartbeat.stop();
1733
1745
  return {
1734
1746
  jsonrpc: '2.0',
1735
1747
  id,
@@ -1745,6 +1757,7 @@ To re-login: /polydev:login`
1745
1757
 
1746
1758
  } catch (error) {
1747
1759
  console.error(`[Stdio Wrapper] CLI tool error:`, error);
1760
+ heartbeat.stop();
1748
1761
  return {
1749
1762
  jsonrpc: '2.0',
1750
1763
  id,
@@ -1885,7 +1898,7 @@ To re-login: /polydev:login`
1885
1898
  * Respects user's perspectives_per_message setting for total perspectives
1886
1899
  * Uses allProviders from dashboard - tries CLI first, falls back to API
1887
1900
  */
1888
- async localSendCliPrompt(args) {
1901
+ async localSendCliPrompt(args, progressToken) {
1889
1902
  console.error(`[Stdio Wrapper] Local CLI prompt sending with perspectives`);
1890
1903
 
1891
1904
  try {
@@ -1912,6 +1925,8 @@ To re-login: /polydev:login`
1912
1925
  } catch (prefError) {
1913
1926
  console.error('[Stdio Wrapper] Model preferences fetch failed (will use CLI defaults):', prefError.message);
1914
1927
  }
1928
+
1929
+ this.sendProgressNotification(progressToken, 15, 100, 'Loaded model preferences');
1915
1930
 
1916
1931
  // Get the user's perspectives_per_message setting (default 2)
1917
1932
  const maxPerspectives = this.perspectivesPerMessage || 2;
@@ -1920,14 +1935,11 @@ To re-login: /polydev:login`
1920
1935
  let localResults = [];
1921
1936
 
1922
1937
  if (provider_id) {
1923
- // Specific provider requested - use only that one
1938
+ // Single provider requested - use only that one
1924
1939
  console.error(`[Stdio Wrapper] Using specific provider: ${provider_id}`);
1925
1940
  const model = modelPreferences[provider_id] || null;
1926
- if (model) {
1927
- console.error(`[Stdio Wrapper] Using user's preferred model for ${provider_id}: ${model}`);
1928
- } else {
1929
- console.error(`[Stdio Wrapper] No model preference for ${provider_id}, using CLI default`);
1930
- }
1941
+ console.error(`[Stdio Wrapper] Using user's preferred model for ${provider_id}: ${model}`);
1942
+ this.sendProgressNotification(progressToken, 20, 100, `Querying ${provider_id}...`);
1931
1943
  const result = await this.cliManager.sendCliPrompt(provider_id, prompt, mode, gracefulTimeout, model);
1932
1944
  localResults = [{ provider_id, ...result }];
1933
1945
  } else {
@@ -1939,6 +1951,7 @@ To re-login: /polydev:login`
1939
1951
  // Get available CLIs for checking
1940
1952
  const { available: availableClis } = await this.getAllAvailableProviders();
1941
1953
  console.error(`[Stdio Wrapper] Available CLIs: ${availableClis.join(', ') || 'none'}`);
1954
+ this.sendProgressNotification(progressToken, 25, 100, `Found ${availableClis.length} CLIs: ${availableClis.join(', ') || 'none'}`);
1942
1955
 
1943
1956
  // Get all providers from dashboard (user's configured providers + CLI-only)
1944
1957
  const allProviders = this.allProviders || [];
@@ -1983,7 +1996,7 @@ To re-login: /polydev:login`
1983
1996
  for (const cliId of cliPriorityOrder) {
1984
1997
  if (!availableClis.includes(cliId)) continue;
1985
1998
  if (cliId === excludedCli) {
1986
- console.error(`[Stdio Wrapper] [CLI-FIRST] Skipping ${cliId} (same as current IDE — would cause recursive call)`);
1999
+ console.error(`[Stdio Wrapper] [CLI-FIRST] Skipping ${excludedCli} (same as current IDE — would cause recursive call)`);
1987
2000
  continue;
1988
2001
  }
1989
2002
 
@@ -2041,6 +2054,7 @@ To re-login: /polydev:login`
2041
2054
  // Run ALL CLI prompts concurrently with fast-collect pattern
2042
2055
  // Resolves once we have maxPerspectives successes OR all complete
2043
2056
  if (cliProviderEntries.length > 0) {
2057
+ this.sendProgressNotification(progressToken, 30, 100, `Querying ${cliProviderEntries.length} CLIs in parallel...`);
2044
2058
  const cliPromises = cliProviderEntries.map(async (providerEntry) => {
2045
2059
  try {
2046
2060
  // ONLY use the model from providerEntry (which is filtered to user's own API keys, not credits)
@@ -2094,6 +2108,7 @@ To re-login: /polydev:login`
2094
2108
  // Fast-collect: resolve early once we have maxPerspectives successes OR all complete
2095
2109
  localResults = await this.collectFirstNSuccesses(cliPromises, maxPerspectives);
2096
2110
  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`);
2111
+ this.sendProgressNotification(progressToken, 55, 100, `Got ${localResults.filter(r => r.success).length} CLI responses`);
2097
2112
  }
2098
2113
 
2099
2114
  // Store API-only providers info for remote API call
@@ -2116,6 +2131,7 @@ To re-login: /polydev:login`
2116
2131
  // Get remote perspectives for API-only providers and failed CLIs
2117
2132
  let perspectivesResult;
2118
2133
  if (remainingPerspectives > 0) {
2134
+ this.sendProgressNotification(progressToken, 65, 100, `Fetching ${remainingPerspectives} more perspectives from API...`);
2119
2135
  console.error(`[Stdio Wrapper] Calling remote API for ${remainingPerspectives} more perspectives (have ${successfulLocalCount}/${maxPerspectives})`);
2120
2136
 
2121
2137
  // Pass API-only provider info to help remote API choose correct models
@@ -2155,6 +2171,7 @@ To re-login: /polydev:login`
2155
2171
  }
2156
2172
 
2157
2173
  // Combine all results
2174
+ this.sendProgressNotification(progressToken, 90, 100, 'Combining all perspectives...');
2158
2175
  return this.combineAllCliAndPerspectives(localResults, perspectivesResult, args);
2159
2176
 
2160
2177
  } catch (error) {
@@ -2462,7 +2479,7 @@ To re-login: /polydev:login`
2462
2479
  user_token: this.userToken,
2463
2480
  // Exclude providers that succeeded locally
2464
2481
  exclude_providers: excludeProviders,
2465
- // NEW: Specific providers to request (from API-only list)
2482
+ // NEW: Specific providers to request (from API-only providers)
2466
2483
  request_providers: requestProviders.length > 0 ? requestProviders : undefined,
2467
2484
  // Pass CLI responses for dashboard logging
2468
2485
  cli_responses: cliResponses,
@@ -2849,11 +2866,11 @@ To re-login: /polydev:login`
2849
2866
  try {
2850
2867
  const homeDir = require('os').homedir();
2851
2868
  const polydevevDir = path.join(homeDir, '.polydev');
2852
- const usageFile = path.join(polydevevDir, 'cli-usage.json');
2869
+ const usageFile = path.join(polydeveevDir, 'cli-usage.json');
2853
2870
 
2854
2871
  // Ensure directory exists
2855
- if (!fs.existsSync(polydevevDir)) {
2856
- fs.mkdirSync(polydevevDir, { recursive: true });
2872
+ if (!fs.existsSync(polydeveevDir)) {
2873
+ fs.mkdirSync(polydeveevDir, { recursive: true });
2857
2874
  }
2858
2875
 
2859
2876
  // Add new usage record
@@ -2963,7 +2980,7 @@ To re-login: /polydev:login`
2963
2980
  this.userModelPreferences = result.modelPreferences;
2964
2981
  this.modelPreferencesCacheTime = Date.now();
2965
2982
 
2966
- // Also cache perspectives_per_message setting (default 2)
2983
+ // Also cache perspectivesPerMessage setting (default 2)
2967
2984
  this.perspectivesPerMessage = result.perspectivesPerMessage || 2;
2968
2985
 
2969
2986
  // Cache provider order from user's dashboard (respects display_order)
@@ -3141,6 +3158,7 @@ To re-login: /polydev:login`
3141
3158
  return this.localForceCliDetection({});
3142
3159
  }).then(() => {
3143
3160
  this._cliDetectionComplete = true;
3161
+ // Display CLI status after detection
3144
3162
  return this.displayCliStatus();
3145
3163
  }).catch((error) => {
3146
3164
  console.error('[Polydev] Auto-login flow error:', error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-ai",
3
- "version": "1.9.26",
3
+ "version": "1.9.27",
4
4
  "engines": {
5
5
  "node": ">=20.x <=22.x"
6
6
  },