polydev-ai 1.9.14 → 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.
@@ -1769,9 +1769,10 @@ Error: ${error.message}`
1769
1769
  const finalProviders = [];
1770
1770
  const usedProviderNames = new Set();
1771
1771
 
1772
- // 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)
1773
1775
  for (const cliId of cliPriorityOrder) {
1774
- if (finalProviders.length >= maxPerspectives) break;
1775
1776
  if (!availableClis.includes(cliId)) continue;
1776
1777
 
1777
1778
  const providerName = cliToProviderMap[cliId];
@@ -1817,7 +1818,7 @@ Error: ${error.message}`
1817
1818
  console.error(`[Stdio Wrapper] [CLI-FIRST] Added API/credits: ${normalizedProvider} (${p.model})${p.tier ? ` [${p.tier}]` : ''}`);
1818
1819
  }
1819
1820
 
1820
- 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(', ')}`);
1821
1822
 
1822
1823
  // Separate into CLI entries (local execution) vs API entries (remote execution)
1823
1824
  const cliProviderEntries = finalProviders.filter(p => p.source === 'cli');
@@ -1825,7 +1826,8 @@ Error: ${error.message}`
1825
1826
 
1826
1827
  console.error(`[Stdio Wrapper] Provider breakdown: CLI=${cliProviderEntries.map(p => p.cliId).join(', ') || 'none'}, API-only=${apiOnlyProviders.map(p => p.provider).join(', ') || 'none'}`);
1827
1828
 
1828
- // 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)
1829
1831
  if (cliProviderEntries.length > 0) {
1830
1832
  const cliPromises = cliProviderEntries.map(async (providerEntry) => {
1831
1833
  try {
@@ -1866,7 +1868,10 @@ Error: ${error.message}`
1866
1868
  };
1867
1869
  }
1868
1870
  });
1869
- 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`);
1870
1875
  }
1871
1876
 
1872
1877
  // Store API-only providers info for remote API call
@@ -1941,6 +1946,42 @@ Error: ${error.message}`
1941
1946
  }
1942
1947
  }
1943
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
+
1944
1985
  /**
1945
1986
  * Get all available and authenticated CLI providers
1946
1987
  * Uses user's provider order from dashboard (display_order) instead of hardcoded order
@@ -2278,54 +2319,6 @@ Error: ${error.message}`
2278
2319
  combineAllCliAndPerspectives(localResults, perspectivesResult, args) {
2279
2320
  // Ensure perspectivesResult is always an object to prevent undefined errors
2280
2321
  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
2322
  let formatted = '';
2330
2323
 
2331
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.14",
3
+ "version": "1.9.15",
4
4
  "engines": {
5
5
  "node": ">=20.x <=22.x"
6
6
  },