polydev-ai 1.9.17 → 1.9.18
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/lib/cliManager.js +10 -0
- package/mcp/stdio-wrapper.js +108 -27
- package/package.json +1 -1
package/lib/cliManager.js
CHANGED
|
@@ -180,6 +180,16 @@ class CLIManager {
|
|
|
180
180
|
return result;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Pre-seed the status cache from external data (e.g., disk cache)
|
|
185
|
+
* This allows instant CLI availability without waiting for detection
|
|
186
|
+
*/
|
|
187
|
+
setCachedStatus(statusMap) {
|
|
188
|
+
for (const [providerId, status] of Object.entries(statusMap)) {
|
|
189
|
+
this.statusCache.set(providerId, status);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
183
193
|
async detectCliProvider(provider) {
|
|
184
194
|
try {
|
|
185
195
|
const cliPath = await this.findCliPath(provider.command);
|
package/mcp/stdio-wrapper.js
CHANGED
|
@@ -304,6 +304,12 @@ class StdioMCPWrapper {
|
|
|
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
|
+
// Adaptive timeout tracking: stores recent response times per CLI
|
|
308
|
+
// Used to set smarter timeouts instead of fixed 240s
|
|
309
|
+
this.cliResponseTimes = {}; // { cliId: [latency_ms, ...] }
|
|
310
|
+
this.CLI_RESPONSE_HISTORY_SIZE = 5; // Keep last 5 response times
|
|
311
|
+
this.loadCliResponseTimes(); // Load from disk
|
|
312
|
+
|
|
307
313
|
// Initialize CLI Manager for local CLI functionality
|
|
308
314
|
// Disable StatusReporter - it's redundant (updateCliStatusInDatabase handles DB updates via /api/cli-status-update)
|
|
309
315
|
// and causes 401 errors because /api/mcp uses different auth than /api/cli-status-update
|
|
@@ -1506,14 +1512,20 @@ Error: ${error.message}`
|
|
|
1506
1512
|
// 2. Calls remote perspectives
|
|
1507
1513
|
// 3. Combines results
|
|
1508
1514
|
const result = await this.localSendCliPrompt(params.arguments);
|
|
1509
|
-
|
|
1515
|
+
|
|
1510
1516
|
// Check if result indicates auth failure (from forwardToRemoteServer 401 handling)
|
|
1511
|
-
|
|
1512
|
-
if (result.result?.content?.[0]?.text?.includes('RE-AUTHENTICATION REQUIRED')) {
|
|
1517
|
+
if (result?.result?.content?.[0]?.text?.includes('RE-AUTHENTICATION REQUIRED')) {
|
|
1513
1518
|
// Auth failure already handled with re-auth response - pass through
|
|
1514
1519
|
return result;
|
|
1515
1520
|
}
|
|
1516
|
-
|
|
1521
|
+
|
|
1522
|
+
// localSendCliPrompt returns either:
|
|
1523
|
+
// - A string (from combineAllCliAndPerspectives) on success
|
|
1524
|
+
// - An object { success, error, timestamp } on exception
|
|
1525
|
+
const resultText = typeof result === 'string'
|
|
1526
|
+
? result
|
|
1527
|
+
: (result.content || this.formatCliResponse(result));
|
|
1528
|
+
|
|
1517
1529
|
return {
|
|
1518
1530
|
jsonrpc: '2.0',
|
|
1519
1531
|
id,
|
|
@@ -1893,31 +1905,41 @@ Error: ${error.message}`
|
|
|
1893
1905
|
if (cliProviderEntries.length > 0) {
|
|
1894
1906
|
const cliPromises = cliProviderEntries.map(async (providerEntry) => {
|
|
1895
1907
|
try {
|
|
1896
|
-
|
|
1908
|
+
// ONLY use the model from providerEntry (which is filtered to user's own API keys, not credits)
|
|
1909
|
+
// Do NOT fall back to modelPreferences[cliId] — it may contain credits-tier model names
|
|
1910
|
+
// (e.g., 'gemini-3-flash') that cause ModelNotFoundError on the actual CLI
|
|
1911
|
+
const model = providerEntry.model || null;
|
|
1897
1912
|
if (model) {
|
|
1898
1913
|
console.error(`[Stdio Wrapper] Using model for ${providerEntry.cliId}: ${model}`);
|
|
1899
1914
|
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1915
|
+
// Use adaptive timeout based on historical response times (instead of fixed 240s)
|
|
1916
|
+
const cliTimeout = this.getAdaptiveTimeout(providerEntry.cliId, gracefulTimeout);
|
|
1917
|
+
let result = await this.cliManager.sendCliPrompt(providerEntry.cliId, prompt, mode, cliTimeout, model);
|
|
1918
|
+
|
|
1902
1919
|
// If CLI failed with a model and the error suggests model issue, retry with CLI default
|
|
1903
1920
|
if (!result.success && model) {
|
|
1904
1921
|
const errorLower = (result.error || '').toLowerCase();
|
|
1905
|
-
const isModelError = errorLower.includes('not found') ||
|
|
1906
|
-
errorLower.includes('not supported') ||
|
|
1922
|
+
const isModelError = errorLower.includes('not found') ||
|
|
1923
|
+
errorLower.includes('not supported') ||
|
|
1907
1924
|
errorLower.includes('invalid model') ||
|
|
1908
1925
|
errorLower.includes('entity was not found') ||
|
|
1909
1926
|
errorLower.includes('does not exist') ||
|
|
1910
1927
|
errorLower.includes('unknown model');
|
|
1911
1928
|
if (isModelError) {
|
|
1912
1929
|
console.error(`[Stdio Wrapper] Model '${model}' failed for ${providerEntry.cliId}, retrying with CLI default...`);
|
|
1913
|
-
result = await this.cliManager.sendCliPrompt(providerEntry.cliId, prompt, mode,
|
|
1930
|
+
result = await this.cliManager.sendCliPrompt(providerEntry.cliId, prompt, mode, cliTimeout, null);
|
|
1914
1931
|
}
|
|
1915
1932
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1933
|
+
|
|
1934
|
+
// Record response time for adaptive timeout calculation
|
|
1935
|
+
if (result.success && result.latency_ms) {
|
|
1936
|
+
this.recordCliResponseTime(providerEntry.cliId, result.latency_ms);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
return {
|
|
1940
|
+
provider_id: providerEntry.cliId,
|
|
1919
1941
|
original_provider: providerEntry.provider,
|
|
1920
|
-
...result
|
|
1942
|
+
...result
|
|
1921
1943
|
};
|
|
1922
1944
|
} catch (error) {
|
|
1923
1945
|
console.error(`[Stdio Wrapper] CLI ${providerEntry.cliId} failed:`, error.message);
|
|
@@ -2213,17 +2235,6 @@ Error: ${error.message}`
|
|
|
2213
2235
|
return this.getModelPreferenceForCli(providerId);
|
|
2214
2236
|
}
|
|
2215
2237
|
|
|
2216
|
-
/**
|
|
2217
|
-
* Get default model name for a CLI tool (used when model not specified in result)
|
|
2218
|
-
* These are just display labels - actual model selection is done by:
|
|
2219
|
-
* 1. User's configured default_model in dashboard API keys
|
|
2220
|
-
* 2. CLI tool's own default if no preference set
|
|
2221
|
-
*/
|
|
2222
|
-
getDefaultModelForCli(providerId) {
|
|
2223
|
-
// Prefer user's model preference if available
|
|
2224
|
-
return this.getModelPreferenceForCli(providerId);
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
2238
|
/**
|
|
2228
2239
|
* Call remote perspectives for CLI prompts
|
|
2229
2240
|
* Only calls remote APIs for providers NOT covered by successful local CLIs
|
|
@@ -2528,6 +2539,56 @@ Error: ${error.message}`
|
|
|
2528
2539
|
}
|
|
2529
2540
|
}
|
|
2530
2541
|
|
|
2542
|
+
/**
|
|
2543
|
+
* Load CLI response times from disk for adaptive timeouts
|
|
2544
|
+
*/
|
|
2545
|
+
loadCliResponseTimes() {
|
|
2546
|
+
try {
|
|
2547
|
+
const timesFile = path.join(os.homedir(), '.polydev', 'cli-response-times.json');
|
|
2548
|
+
if (fs.existsSync(timesFile)) {
|
|
2549
|
+
this.cliResponseTimes = JSON.parse(fs.readFileSync(timesFile, 'utf8'));
|
|
2550
|
+
console.error(`[Stdio Wrapper] Loaded CLI response times from disk`);
|
|
2551
|
+
}
|
|
2552
|
+
} catch (e) {
|
|
2553
|
+
// Non-critical
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
/**
|
|
2558
|
+
* Record a CLI response time for adaptive timeout calculation
|
|
2559
|
+
*/
|
|
2560
|
+
recordCliResponseTime(cliId, latencyMs) {
|
|
2561
|
+
if (!this.cliResponseTimes[cliId]) {
|
|
2562
|
+
this.cliResponseTimes[cliId] = [];
|
|
2563
|
+
}
|
|
2564
|
+
this.cliResponseTimes[cliId].push(latencyMs);
|
|
2565
|
+
// Keep only the last N entries
|
|
2566
|
+
if (this.cliResponseTimes[cliId].length > this.CLI_RESPONSE_HISTORY_SIZE) {
|
|
2567
|
+
this.cliResponseTimes[cliId] = this.cliResponseTimes[cliId].slice(-this.CLI_RESPONSE_HISTORY_SIZE);
|
|
2568
|
+
}
|
|
2569
|
+
// Save to disk (fire-and-forget)
|
|
2570
|
+
try {
|
|
2571
|
+
const timesFile = path.join(os.homedir(), '.polydev', 'cli-response-times.json');
|
|
2572
|
+
fs.writeFileSync(timesFile, JSON.stringify(this.cliResponseTimes));
|
|
2573
|
+
} catch (e) { /* non-critical */ }
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
/**
|
|
2577
|
+
* Get adaptive timeout for a CLI based on historical response times
|
|
2578
|
+
* Returns timeout in ms (2.5x the average, with min 30s and max 240s)
|
|
2579
|
+
*/
|
|
2580
|
+
getAdaptiveTimeout(cliId, defaultTimeout = 240000) {
|
|
2581
|
+
const times = this.cliResponseTimes[cliId];
|
|
2582
|
+
if (!times || times.length === 0) {
|
|
2583
|
+
return defaultTimeout; // No history, use default
|
|
2584
|
+
}
|
|
2585
|
+
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
2586
|
+
// 2.5x average, clamped between 30s and 240s
|
|
2587
|
+
const adaptive = Math.min(240000, Math.max(30000, Math.round(avg * 2.5)));
|
|
2588
|
+
console.error(`[Stdio Wrapper] Adaptive timeout for ${cliId}: ${adaptive}ms (avg: ${Math.round(avg)}ms from ${times.length} samples)`);
|
|
2589
|
+
return adaptive;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2531
2592
|
/**
|
|
2532
2593
|
* Load CLI status from local file cache
|
|
2533
2594
|
*/
|
|
@@ -2680,8 +2741,11 @@ Error: ${error.message}`
|
|
|
2680
2741
|
* Format CLI response for MCP output
|
|
2681
2742
|
*/
|
|
2682
2743
|
formatCliResponse(result) {
|
|
2744
|
+
// Handle string results (from combineAllCliAndPerspectives)
|
|
2745
|
+
if (typeof result === 'string') return result;
|
|
2746
|
+
|
|
2683
2747
|
if (!result.success) {
|
|
2684
|
-
return `❌ **CLI Error**\n\n${result.error}\n\n*Timestamp: ${result.timestamp}*`;
|
|
2748
|
+
return `❌ **CLI Error**\n\n${result.error || 'Unknown error'}\n\n*Timestamp: ${result.timestamp || new Date().toISOString()}*`;
|
|
2685
2749
|
}
|
|
2686
2750
|
|
|
2687
2751
|
// Handle combined CLI + perspectives response (single or multiple CLIs)
|
|
@@ -2879,8 +2943,25 @@ Error: ${error.message}`
|
|
|
2879
2943
|
// Only run CLI detection here if we already have a token
|
|
2880
2944
|
// (If no token, CLI detection runs after login completes in runStartupFlow)
|
|
2881
2945
|
if (this.userToken) {
|
|
2946
|
+
// Pre-seed CLI status from disk cache for instant availability
|
|
2947
|
+
// This eliminates the 3-7s startup penalty from shelling out to CLI --version
|
|
2948
|
+
try {
|
|
2949
|
+
const cachedStatus = await this.loadLocalCliStatus();
|
|
2950
|
+
if (cachedStatus && Object.keys(cachedStatus).length > 0) {
|
|
2951
|
+
console.error(`[Polydev] Pre-seeded CLI status from disk cache (${Object.keys(cachedStatus).length} providers)`);
|
|
2952
|
+
this.cliManager.setCachedStatus(cachedStatus);
|
|
2953
|
+
this._cliDetectionComplete = true; // Mark as complete so requests don't wait
|
|
2954
|
+
if (this._cliDetectionResolver) {
|
|
2955
|
+
this._cliDetectionResolver(); // Unblock any waiting requests
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
} catch (e) {
|
|
2959
|
+
console.error('[Polydev] Disk cache pre-seed failed (will detect fresh):', e.message);
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
// Run full CLI detection in background to refresh the cache
|
|
2882
2963
|
console.error('[Polydev] Detecting local CLI tools...');
|
|
2883
|
-
|
|
2964
|
+
|
|
2884
2965
|
this.localForceCliDetection({})
|
|
2885
2966
|
.then(async () => {
|
|
2886
2967
|
this._cliDetectionComplete = true;
|