polydev-ai 1.9.13 → 1.9.15

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 +168 -70
  2. package/package.json +1 -1
@@ -585,7 +585,16 @@ To re-login: npx polydev-ai`
585
585
  // if this process dies, the new one can resume polling with same session)
586
586
  this.startLoginPolling(sessionId);
587
587
 
588
- // Return immediately with accurate status
588
+ // Wait for authentication to complete (blocks up to 120s)
589
+ console.error('[Polydev] Waiting for browser authentication to complete...');
590
+ const authenticated = await this.waitForAuthCompletion();
591
+
592
+ if (authenticated) {
593
+ console.error('[Polydev] Login successful, returning status...');
594
+ return await this.fetchAuthStatusResponse(id);
595
+ }
596
+
597
+ // Timeout - return fallback message
589
598
  return {
590
599
  jsonrpc: '2.0',
591
600
  id,
@@ -595,20 +604,12 @@ To re-login: npx polydev-ai`
595
604
  text: `AUTHENTICATION STARTED
596
605
  ======================
597
606
 
598
- A browser window is opening for you to sign in.
607
+ A browser window was opened for you to sign in.
599
608
 
600
- If it doesn't open, visit:
609
+ If it didn't open, visit:
601
610
  ${authUrl}
602
611
 
603
- Once you sign in, your token will be saved automatically to:
604
- ~/.polydev.env
605
- ~/.zshrc
606
-
607
- After login completes, you can:
608
- /polydev:ask Query multiple AI models
609
- /polydev:auth Check status & credits
610
-
611
- Dashboard: https://polydev.ai/dashboard`
612
+ Still waiting for login. Use /polydev:auth to check status after signing in.`
612
613
  }]
613
614
  }
614
615
  };
@@ -658,8 +659,102 @@ Dashboard: https://polydev.ai/dashboard`
658
659
  }
659
660
  }
660
661
 
662
+ /**
663
+ * Wait for authentication to complete by polling this.isAuthenticated
664
+ * Used to block tool calls until login is detected by startLoginPolling
665
+ * @param {number} timeoutMs - Max time to wait (default 120s)
666
+ * @returns {boolean} true if authenticated, false if timed out
667
+ */
668
+ async waitForAuthCompletion(timeoutMs = 120000) {
669
+ const pollInterval = 3000; // Check every 3 seconds
670
+ const maxPolls = Math.floor(timeoutMs / pollInterval);
671
+
672
+ for (let i = 0; i < maxPolls; i++) {
673
+ await new Promise(r => setTimeout(r, pollInterval));
674
+ if (this.isAuthenticated && this.userToken) {
675
+ return true;
676
+ }
677
+ }
678
+ return false;
679
+ }
680
+
681
+ /**
682
+ * Fetch account status and return formatted response
683
+ * Standalone helper that doesn't trigger re-auth (avoids circular calls)
684
+ * @param {*} id - JSON-RPC request id
685
+ * @returns {object} JSON-RPC response with account status
686
+ */
687
+ async fetchAuthStatusResponse(id) {
688
+ try {
689
+ const accountResponse = await fetch('https://www.polydev.ai/api/auth/status', {
690
+ method: 'POST',
691
+ headers: {
692
+ 'Content-Type': 'application/json',
693
+ 'Authorization': `Bearer ${this.userToken}`,
694
+ 'User-Agent': 'polydev-stdio-wrapper/1.0.0'
695
+ }
696
+ });
697
+
698
+ if (accountResponse.ok) {
699
+ const data = await accountResponse.json();
700
+ const credits = data.credits_remaining?.toLocaleString() || 0;
701
+ const messages = data.messages_remaining?.toLocaleString() || 0;
702
+ const tier = data.subscription_tier || 'Free';
703
+ const email = data.email || 'Connected';
704
+
705
+ return {
706
+ jsonrpc: '2.0',
707
+ id,
708
+ result: {
709
+ content: [{
710
+ type: 'text',
711
+ text: `LOGIN SUCCESSFUL
712
+ ================
713
+
714
+ Authentication: Connected
715
+ Account: ${email}
716
+ Credits: ${credits}
717
+ Messages: ${messages}
718
+ Tier: ${tier}
719
+
720
+ You can now use:
721
+ /polydev:ask Query multiple AI models
722
+ /polydev:auth Check full status & credits
723
+
724
+ Dashboard: https://polydev.ai/dashboard`
725
+ }]
726
+ }
727
+ };
728
+ }
729
+ } catch (e) {
730
+ console.error('[Polydev] Post-login status fetch failed:', e.message);
731
+ }
732
+
733
+ // Fallback if status fetch fails
734
+ return {
735
+ jsonrpc: '2.0',
736
+ id,
737
+ result: {
738
+ content: [{
739
+ type: 'text',
740
+ text: `LOGIN SUCCESSFUL
741
+ ================
742
+
743
+ Your token has been saved and is active.
744
+
745
+ You can now use:
746
+ /polydev:ask Query multiple AI models
747
+ /polydev:auth Check full status & credits
748
+
749
+ Dashboard: https://polydev.ai/dashboard`
750
+ }]
751
+ }
752
+ };
753
+ }
754
+
661
755
  /**
662
756
  * Trigger re-authentication by opening browser and starting polling
757
+ * Waits for auth to complete inline (up to 120s) then returns full status
663
758
  * Used when token is detected as invalid/expired
664
759
  * @param {*} id - JSON-RPC request id
665
760
  * @param {string} reason - Human-readable reason for re-auth
@@ -703,23 +798,33 @@ Dashboard: https://polydev.ai/dashboard`
703
798
  this.savePendingSession(sessionId);
704
799
  this.startLoginPolling(sessionId);
705
800
 
801
+ // Wait for authentication to complete (blocks up to 120s)
802
+ console.error('[Polydev] Waiting for browser authentication to complete...');
803
+ const authenticated = await this.waitForAuthCompletion();
804
+
805
+ if (authenticated) {
806
+ console.error('[Polydev] Re-authentication successful, returning status...');
807
+ return await this.fetchAuthStatusResponse(id);
808
+ }
809
+
810
+ // Timeout - return fallback message
706
811
  return {
707
812
  jsonrpc: '2.0',
708
813
  id,
709
814
  result: {
710
815
  content: [{
711
816
  type: 'text',
712
- text: `RE-AUTHENTICATION REQUIRED
713
- ==========================
817
+ text: `RE-AUTHENTICATION STARTED
818
+ =========================
714
819
 
715
820
  ${reason}
716
821
 
717
- A browser window is opening to re-authenticate.
822
+ A browser window was opened for re-authentication.
718
823
 
719
- If it doesn't open, visit:
824
+ If it didn't open, visit:
720
825
  ${authUrl}
721
826
 
722
- Once you sign in, your token will be updated automatically.`
827
+ Still waiting for login. Use /polydev:auth to check status after signing in.`
723
828
  }]
724
829
  }
725
830
  };
@@ -1664,9 +1769,10 @@ Error: ${error.message}`
1664
1769
  const finalProviders = [];
1665
1770
  const usedProviderNames = new Set();
1666
1771
 
1667
- // STEP 1: Add ALL available CLIs first (in priority order) - they're FREE
1772
+ // STEP 1: Add ALL available CLIs (in priority order) - they're FREE
1773
+ // Don't limit to maxPerspectives here — we run all CLIs in parallel
1774
+ // and take the first maxPerspectives successes (fast-collect pattern)
1668
1775
  for (const cliId of cliPriorityOrder) {
1669
- if (finalProviders.length >= maxPerspectives) break;
1670
1776
  if (!availableClis.includes(cliId)) continue;
1671
1777
 
1672
1778
  const providerName = cliToProviderMap[cliId];
@@ -1712,7 +1818,7 @@ Error: ${error.message}`
1712
1818
  console.error(`[Stdio Wrapper] [CLI-FIRST] Added API/credits: ${normalizedProvider} (${p.model})${p.tier ? ` [${p.tier}]` : ''}`);
1713
1819
  }
1714
1820
 
1715
- console.error(`[Stdio Wrapper] Final provider list (${finalProviders.length}/${maxPerspectives}): ${finalProviders.map(p => `${p.provider}[${p.source}]`).join(', ')}`);
1821
+ console.error(`[Stdio Wrapper] Final provider list (${finalProviders.length}, need ${maxPerspectives}): ${finalProviders.map(p => `${p.provider}[${p.source}]`).join(', ')}`);
1716
1822
 
1717
1823
  // Separate into CLI entries (local execution) vs API entries (remote execution)
1718
1824
  const cliProviderEntries = finalProviders.filter(p => p.source === 'cli');
@@ -1720,7 +1826,8 @@ Error: ${error.message}`
1720
1826
 
1721
1827
  console.error(`[Stdio Wrapper] Provider breakdown: CLI=${cliProviderEntries.map(p => p.cliId).join(', ') || 'none'}, API-only=${apiOnlyProviders.map(p => p.provider).join(', ') || 'none'}`);
1722
1828
 
1723
- // Run all CLI prompts concurrently
1829
+ // Run ALL CLI prompts concurrently with fast-collect pattern
1830
+ // Resolves once we have maxPerspectives successes (don't wait for slow CLIs)
1724
1831
  if (cliProviderEntries.length > 0) {
1725
1832
  const cliPromises = cliProviderEntries.map(async (providerEntry) => {
1726
1833
  try {
@@ -1761,7 +1868,10 @@ Error: ${error.message}`
1761
1868
  };
1762
1869
  }
1763
1870
  });
1764
- localResults = await Promise.all(cliPromises);
1871
+
1872
+ // Fast-collect: resolve once we have maxPerspectives successes OR all complete
1873
+ localResults = await this.collectFirstNSuccesses(cliPromises, maxPerspectives);
1874
+ 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`);
1765
1875
  }
1766
1876
 
1767
1877
  // Store API-only providers info for remote API call
@@ -1836,6 +1946,42 @@ Error: ${error.message}`
1836
1946
  }
1837
1947
  }
1838
1948
 
1949
+ /**
1950
+ * Collect results from parallel promises, resolving early once we have N successes
1951
+ * This avoids waiting for slow/timed-out CLIs when we already have enough results
1952
+ * @param {Promise[]} promises - Array of promises to collect from
1953
+ * @param {number} needed - Number of successful results needed
1954
+ * @returns {Promise<Array>} Array of results (may include failures if not enough successes)
1955
+ */
1956
+ collectFirstNSuccesses(promises, needed) {
1957
+ return new Promise((resolve) => {
1958
+ const results = [];
1959
+ let successCount = 0;
1960
+ let completedCount = 0;
1961
+ let resolved = false;
1962
+
1963
+ if (promises.length === 0) {
1964
+ resolve([]);
1965
+ return;
1966
+ }
1967
+
1968
+ for (const promise of promises) {
1969
+ promise.then(result => {
1970
+ if (resolved) return;
1971
+ results.push(result);
1972
+ if (result.success) successCount++;
1973
+ completedCount++;
1974
+
1975
+ // Resolve early if we have enough successes OR all promises done
1976
+ if (successCount >= needed || completedCount >= promises.length) {
1977
+ resolved = true;
1978
+ resolve([...results]); // Copy to prevent mutation from late arrivals
1979
+ }
1980
+ });
1981
+ }
1982
+ });
1983
+ }
1984
+
1839
1985
  /**
1840
1986
  * Get all available and authenticated CLI providers
1841
1987
  * Uses user's provider order from dashboard (display_order) instead of hardcoded order
@@ -2173,54 +2319,6 @@ Error: ${error.message}`
2173
2319
  combineAllCliAndPerspectives(localResults, perspectivesResult, args) {
2174
2320
  // Ensure perspectivesResult is always an object to prevent undefined errors
2175
2321
  const safePersp = perspectivesResult || { success: false, error: 'No response from perspectives server' };
2176
-
2177
- const combinedResult = {
2178
- success: true,
2179
- timestamp: new Date().toISOString(),
2180
- mode: args.mode,
2181
- local_cli_count: localResults.length,
2182
- sections: {
2183
- local: localResults,
2184
- remote: safePersp
2185
- }
2186
- };
2187
-
2188
- // Check if any local CLIs succeeded
2189
- const successfulClis = localResults.filter(result => result.success);
2190
- const hasSomeLocalSuccess = successfulClis.length > 0;
2191
-
2192
- // Determine overall success and content
2193
- if (hasSomeLocalSuccess && safePersp.success) {
2194
- combinedResult.content = this.formatMultipleCliResponse(localResults, safePersp, false);
2195
- combinedResult.tokens_used = successfulClis.reduce((total, cli) => total + (cli.tokens_used || 0), 0);
2196
- combinedResult.latency_ms = Math.max(...successfulClis.map(cli => cli.latency_ms || 0));
2197
- } else if (!hasSomeLocalSuccess && safePersp.success) {
2198
- // Complete fallback case - no local CLIs worked
2199
- combinedResult.content = this.formatMultipleCliResponse(localResults, safePersp, true);
2200
- combinedResult.fallback_used = true;
2201
- combinedResult.tokens_used = 0; // No local tokens used
2202
- } else if (hasSomeLocalSuccess && !safePersp.success) {
2203
- // Local CLIs succeeded, remote failed
2204
- combinedResult.content = this.formatMultipleCliResponse(localResults, safePersp, false);
2205
- combinedResult.tokens_used = successfulClis.reduce((total, cli) => total + (cli.tokens_used || 0), 0);
2206
- combinedResult.latency_ms = Math.max(...successfulClis.map(cli => cli.latency_ms || 0));
2207
- } else {
2208
- // Both failed
2209
- combinedResult.success = false;
2210
- const cliErrors = localResults.map(cli => `${cli.provider_id}: ${cli.error || 'Unknown error'}`).join('; ');
2211
- const perspectivesError = safePersp.error || 'Unknown remote error';
2212
- combinedResult.error = `All local CLIs failed: ${cliErrors}; Perspectives also failed: ${perspectivesError}`;
2213
- }
2214
-
2215
- return combinedResult;
2216
- }
2217
-
2218
- /**
2219
- * Format multiple CLI responses with remote perspectives
2220
- */
2221
- formatMultipleCliResponse(localResults, perspectivesResult, isFallback) {
2222
- // Safety check - ensure perspectivesResult is always an object
2223
- const safePersp = perspectivesResult || { success: false, error: 'No perspectives data' };
2224
2322
  let formatted = '';
2225
2323
 
2226
2324
  // Show all local CLI responses
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-ai",
3
- "version": "1.9.13",
3
+ "version": "1.9.15",
4
4
  "engines": {
5
5
  "node": ">=20.x <=22.x"
6
6
  },