polydev-ai 1.2.0 → 1.2.2
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/mcp/manifest.json +1 -1
- package/mcp/stdio-wrapper.js +235 -34
- package/package.json +2 -1
package/mcp/manifest.json
CHANGED
package/mcp/stdio-wrapper.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Lightweight stdio wrapper with local CLI functionality and remote Polydev MCP server fallback
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const CLIManager = require('../lib/cliManager')
|
|
6
|
+
const { CLIManager } = require('../lib/cliManager');
|
|
7
7
|
|
|
8
8
|
class StdioMCPWrapper {
|
|
9
9
|
constructor() {
|
|
@@ -204,7 +204,7 @@ class StdioMCPWrapper {
|
|
|
204
204
|
const providerId = args.provider_id; // Optional - detect specific provider
|
|
205
205
|
|
|
206
206
|
// Force detection using CLI Manager (no remote API calls)
|
|
207
|
-
const results = await this.cliManager.forceCliDetection(
|
|
207
|
+
const results = await this.cliManager.forceCliDetection(providerId);
|
|
208
208
|
|
|
209
209
|
// Save status locally to file-based cache
|
|
210
210
|
await this.saveLocalCliStatus(results);
|
|
@@ -241,13 +241,13 @@ class StdioMCPWrapper {
|
|
|
241
241
|
if (providerId) {
|
|
242
242
|
// Get specific provider status
|
|
243
243
|
const status = await this.cliManager.getCliStatus(providerId);
|
|
244
|
-
results
|
|
244
|
+
results = status;
|
|
245
245
|
} else {
|
|
246
246
|
// Get all providers status
|
|
247
|
-
const providers = this.cliManager.
|
|
247
|
+
const providers = this.cliManager.getAvailableProviders();
|
|
248
248
|
for (const provider of providers) {
|
|
249
249
|
const status = await this.cliManager.getCliStatus(provider.id);
|
|
250
|
-
results[provider.id] = status;
|
|
250
|
+
results[provider.id] = status[provider.id];
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
@@ -271,41 +271,55 @@ class StdioMCPWrapper {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
/**
|
|
274
|
-
* Local CLI prompt sending
|
|
274
|
+
* Local CLI prompt sending with remote perspectives fallback/supplement
|
|
275
275
|
*/
|
|
276
276
|
async localSendCliPrompt(args) {
|
|
277
|
-
console.error(`[Stdio Wrapper] Local CLI prompt sending`);
|
|
277
|
+
console.error(`[Stdio Wrapper] Local CLI prompt sending with perspectives`);
|
|
278
278
|
|
|
279
279
|
try {
|
|
280
|
-
|
|
280
|
+
let { provider_id, prompt, mode = 'args', timeout_ms = 30000 } = args;
|
|
281
281
|
|
|
282
|
-
if (!
|
|
283
|
-
throw new Error('
|
|
282
|
+
if (!prompt) {
|
|
283
|
+
throw new Error('prompt is required');
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
provider_id
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
timeout_ms
|
|
292
|
-
);
|
|
286
|
+
// Auto-select best available provider if none specified
|
|
287
|
+
if (!provider_id) {
|
|
288
|
+
provider_id = await this.selectBestProvider();
|
|
289
|
+
console.error(`[Stdio Wrapper] Auto-selected provider: ${provider_id}`);
|
|
290
|
+
}
|
|
293
291
|
|
|
294
|
-
//
|
|
295
|
-
|
|
292
|
+
// Use shorter timeout for faster fallback (5 seconds instead of 30)
|
|
293
|
+
const gracefulTimeout = Math.min(timeout_ms, 5000);
|
|
294
|
+
|
|
295
|
+
// Start both operations concurrently for better performance
|
|
296
|
+
const [localResult, perspectivesResult] = await Promise.allSettled([
|
|
297
|
+
this.cliManager.sendCliPrompt(provider_id, prompt, mode, gracefulTimeout),
|
|
298
|
+
this.callPerspectivesForCli(args, null)
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
// Process results
|
|
302
|
+
const localResponse = localResult.status === 'fulfilled' ? localResult.value : {
|
|
303
|
+
success: false,
|
|
304
|
+
error: `CLI check failed: ${localResult.reason?.message || 'Unknown error'}`,
|
|
305
|
+
latency_ms: gracefulTimeout,
|
|
306
|
+
timestamp: new Date().toISOString()
|
|
307
|
+
};
|
|
296
308
|
|
|
297
|
-
|
|
298
|
-
success:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
tokens_used: response.tokensUsed,
|
|
302
|
-
latency_ms: response.latencyMs,
|
|
303
|
-
provider: provider_id,
|
|
304
|
-
mode,
|
|
305
|
-
timestamp: new Date().toISOString(),
|
|
306
|
-
local_only: true
|
|
309
|
+
const perspectivesResponse = perspectivesResult.status === 'fulfilled' ? perspectivesResult.value : {
|
|
310
|
+
success: false,
|
|
311
|
+
error: `Perspectives failed: ${perspectivesResult.reason?.message || 'Unknown error'}`,
|
|
312
|
+
timestamp: new Date().toISOString()
|
|
307
313
|
};
|
|
308
314
|
|
|
315
|
+
// Record usage locally (file-based analytics) - non-blocking
|
|
316
|
+
this.recordLocalUsage(provider_id, prompt, localResponse).catch(err => {
|
|
317
|
+
console.error('[Stdio Wrapper] Usage recording failed (non-critical):', err.message);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Combine results
|
|
321
|
+
return this.combineCliAndPerspectives(localResponse, perspectivesResponse, args);
|
|
322
|
+
|
|
309
323
|
} catch (error) {
|
|
310
324
|
console.error('[Stdio Wrapper] Local CLI prompt error:', error);
|
|
311
325
|
return {
|
|
@@ -317,6 +331,177 @@ class StdioMCPWrapper {
|
|
|
317
331
|
}
|
|
318
332
|
}
|
|
319
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Select the best available CLI provider automatically
|
|
336
|
+
*/
|
|
337
|
+
async selectBestProvider() {
|
|
338
|
+
try {
|
|
339
|
+
const allStatus = await this.cliManager.getCliStatus();
|
|
340
|
+
|
|
341
|
+
// Priority order: claude_code > codex_cli > gemini_cli
|
|
342
|
+
const priorityOrder = ['claude_code', 'codex_cli', 'gemini_cli'];
|
|
343
|
+
|
|
344
|
+
for (const providerId of priorityOrder) {
|
|
345
|
+
const status = allStatus[providerId];
|
|
346
|
+
if (status && status.available && status.authenticated) {
|
|
347
|
+
return providerId;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// If no authenticated provider, return the first available one
|
|
352
|
+
for (const providerId of priorityOrder) {
|
|
353
|
+
const status = allStatus[providerId];
|
|
354
|
+
if (status && status.available) {
|
|
355
|
+
return providerId;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Default fallback to claude_code (will trigger remote perspectives)
|
|
360
|
+
return 'claude_code';
|
|
361
|
+
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.error('[Stdio Wrapper] Provider selection failed, defaulting to claude_code:', error);
|
|
364
|
+
return 'claude_code';
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Call remote perspectives for CLI prompts
|
|
370
|
+
*/
|
|
371
|
+
async callPerspectivesForCli(args, localResult) {
|
|
372
|
+
console.error(`[Stdio Wrapper] Calling remote perspectives for CLI prompt`);
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const perspectivesRequest = {
|
|
376
|
+
jsonrpc: '2.0',
|
|
377
|
+
method: 'tools/call',
|
|
378
|
+
params: {
|
|
379
|
+
name: 'get_perspectives',
|
|
380
|
+
arguments: {
|
|
381
|
+
prompt: args.prompt,
|
|
382
|
+
user_token: this.userToken,
|
|
383
|
+
// Let the remote server use user's configured preferences for models
|
|
384
|
+
// Don't specify models to use dashboard defaults
|
|
385
|
+
project_memory: 'none',
|
|
386
|
+
temperature: 0.7,
|
|
387
|
+
max_tokens: 2000
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
id: `perspectives-${Date.now()}`
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const remoteResponse = await this.forwardToRemoteServer(perspectivesRequest);
|
|
394
|
+
|
|
395
|
+
if (remoteResponse.result && remoteResponse.result.content && remoteResponse.result.content[0]) {
|
|
396
|
+
// The remote response already contains formatted "Multiple AI Perspectives" content
|
|
397
|
+
// Return it as-is without additional formatting to avoid duplication
|
|
398
|
+
const rawContent = remoteResponse.result.content[0].text;
|
|
399
|
+
return {
|
|
400
|
+
success: true,
|
|
401
|
+
content: rawContent,
|
|
402
|
+
timestamp: new Date().toISOString(),
|
|
403
|
+
raw: true // Flag to indicate this is pre-formatted content
|
|
404
|
+
};
|
|
405
|
+
} else if (remoteResponse.error) {
|
|
406
|
+
return {
|
|
407
|
+
success: false,
|
|
408
|
+
error: remoteResponse.error.message || 'Remote perspectives failed',
|
|
409
|
+
timestamp: new Date().toISOString()
|
|
410
|
+
};
|
|
411
|
+
} else {
|
|
412
|
+
return {
|
|
413
|
+
success: false,
|
|
414
|
+
error: 'Unexpected remote response format',
|
|
415
|
+
timestamp: new Date().toISOString()
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error('[Stdio Wrapper] Perspectives call error:', error);
|
|
420
|
+
return {
|
|
421
|
+
success: false,
|
|
422
|
+
error: `Perspectives request failed: ${error.message}`,
|
|
423
|
+
timestamp: new Date().toISOString()
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Combine local CLI and remote perspectives results
|
|
430
|
+
*/
|
|
431
|
+
combineCliAndPerspectives(localResult, perspectivesResult, args) {
|
|
432
|
+
const combinedResult = {
|
|
433
|
+
success: true,
|
|
434
|
+
timestamp: new Date().toISOString(),
|
|
435
|
+
provider: args.provider_id,
|
|
436
|
+
mode: args.mode,
|
|
437
|
+
sections: {
|
|
438
|
+
local: localResult,
|
|
439
|
+
remote: perspectivesResult
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// Determine overall success and fallback status
|
|
444
|
+
if (localResult.success && perspectivesResult.success) {
|
|
445
|
+
combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, false);
|
|
446
|
+
combinedResult.tokens_used = localResult.tokens_used || 0;
|
|
447
|
+
combinedResult.latency_ms = localResult.latency_ms || 0;
|
|
448
|
+
} else if (!localResult.success && perspectivesResult.success) {
|
|
449
|
+
// Fallback case
|
|
450
|
+
combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, true);
|
|
451
|
+
combinedResult.fallback_used = true;
|
|
452
|
+
combinedResult.tokens_used = 0; // No local tokens used
|
|
453
|
+
} else if (localResult.success && !perspectivesResult.success) {
|
|
454
|
+
// Local succeeded, remote failed
|
|
455
|
+
combinedResult.content = this.formatCombinedResponse(localResult, perspectivesResult, false);
|
|
456
|
+
combinedResult.tokens_used = localResult.tokens_used || 0;
|
|
457
|
+
combinedResult.latency_ms = localResult.latency_ms || 0;
|
|
458
|
+
} else {
|
|
459
|
+
// Both failed
|
|
460
|
+
combinedResult.success = false;
|
|
461
|
+
combinedResult.error = `Local CLI failed: ${localResult.error}; Perspectives also failed: ${perspectivesResult.error}`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return combinedResult;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Format combined response text
|
|
469
|
+
*/
|
|
470
|
+
formatCombinedResponse(localResult, perspectivesResult, isFallback) {
|
|
471
|
+
let formatted = '';
|
|
472
|
+
|
|
473
|
+
if (localResult.success) {
|
|
474
|
+
// Local CLI succeeded
|
|
475
|
+
formatted += `🟢 **Local CLI Response** (${localResult.provider} - ${localResult.mode} mode)\n\n`;
|
|
476
|
+
formatted += `${localResult.content}\n\n`;
|
|
477
|
+
formatted += `*Latency: ${localResult.latency_ms || 0}ms | Tokens: ${localResult.tokens_used || 0}*\n\n`;
|
|
478
|
+
formatted += `---\n\n`;
|
|
479
|
+
} else if (isFallback) {
|
|
480
|
+
// Local CLI failed, using fallback
|
|
481
|
+
formatted += `⚠️ **Local CLI unavailable**: ${localResult.error}\n`;
|
|
482
|
+
formatted += `Using perspectives fallback.\n\n`;
|
|
483
|
+
formatted += `---\n\n`;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (perspectivesResult.success) {
|
|
487
|
+
if (perspectivesResult.raw) {
|
|
488
|
+
// Raw content is already formatted - use as-is without any additional title
|
|
489
|
+
// The remote server already includes proper headers like "Multiple AI Perspectives"
|
|
490
|
+
formatted += `${perspectivesResult.content}\n\n`;
|
|
491
|
+
} else {
|
|
492
|
+
// Legacy formatting for non-raw content (shouldn't be used with current server)
|
|
493
|
+
const title = isFallback ? '🧠 **Perspectives Fallback**' : '🧠 **Supplemental Multi-Model Perspectives**';
|
|
494
|
+
formatted += `${title}\n\n`;
|
|
495
|
+
formatted += `${perspectivesResult.content}\n\n`;
|
|
496
|
+
}
|
|
497
|
+
} else if (!isFallback) {
|
|
498
|
+
// Show remote error only if not in fallback mode
|
|
499
|
+
formatted += `❌ **Perspectives request failed**: ${perspectivesResult.error}\n\n`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return formatted.trim();
|
|
503
|
+
}
|
|
504
|
+
|
|
320
505
|
/**
|
|
321
506
|
* Save CLI status to local file cache
|
|
322
507
|
*/
|
|
@@ -354,10 +539,20 @@ class StdioMCPWrapper {
|
|
|
354
539
|
const polydevevDir = path.join(homeDir, '.polydev');
|
|
355
540
|
const usageFile = path.join(polydevevDir, 'cli-usage.json');
|
|
356
541
|
|
|
542
|
+
// Ensure directory exists
|
|
543
|
+
if (!fs.existsSync(polydevevDir)) {
|
|
544
|
+
fs.mkdirSync(polydevevDir, { recursive: true });
|
|
545
|
+
}
|
|
546
|
+
|
|
357
547
|
// Load existing usage data
|
|
358
548
|
let usageData = [];
|
|
359
549
|
if (fs.existsSync(usageFile)) {
|
|
360
|
-
|
|
550
|
+
try {
|
|
551
|
+
usageData = JSON.parse(fs.readFileSync(usageFile, 'utf8'));
|
|
552
|
+
} catch (parseError) {
|
|
553
|
+
console.error('[Stdio Wrapper] Failed to parse existing usage file, starting fresh:', parseError);
|
|
554
|
+
usageData = [];
|
|
555
|
+
}
|
|
361
556
|
}
|
|
362
557
|
|
|
363
558
|
// Add new usage record
|
|
@@ -366,8 +561,8 @@ class StdioMCPWrapper {
|
|
|
366
561
|
provider: providerId,
|
|
367
562
|
prompt_length: prompt.length,
|
|
368
563
|
success: response.success,
|
|
369
|
-
latency_ms: response.latencyMs,
|
|
370
|
-
tokens_used: response.tokensUsed || 0
|
|
564
|
+
latency_ms: response.latency_ms || response.latencyMs || 0,
|
|
565
|
+
tokens_used: response.tokens_used || response.tokensUsed || 0
|
|
371
566
|
});
|
|
372
567
|
|
|
373
568
|
// Keep only last 1000 records
|
|
@@ -379,7 +574,8 @@ class StdioMCPWrapper {
|
|
|
379
574
|
console.error(`[Stdio Wrapper] Usage recorded locally`);
|
|
380
575
|
|
|
381
576
|
} catch (error) {
|
|
382
|
-
console.error('[Stdio Wrapper] Failed to record local usage:', error);
|
|
577
|
+
console.error('[Stdio Wrapper] Failed to record local usage (non-critical):', error.message);
|
|
578
|
+
// Don't throw - this is non-critical functionality
|
|
383
579
|
}
|
|
384
580
|
}
|
|
385
581
|
|
|
@@ -391,8 +587,13 @@ class StdioMCPWrapper {
|
|
|
391
587
|
return `❌ **CLI Error**\n\n${result.error}\n\n*Timestamp: ${result.timestamp}*`;
|
|
392
588
|
}
|
|
393
589
|
|
|
590
|
+
// Handle combined CLI + perspectives response
|
|
591
|
+
if (result.sections) {
|
|
592
|
+
return result.content;
|
|
593
|
+
}
|
|
594
|
+
|
|
394
595
|
if (result.content) {
|
|
395
|
-
//
|
|
596
|
+
// Standard prompt response
|
|
396
597
|
return `✅ **CLI Response** (${result.provider || 'Unknown'} - ${result.mode || 'unknown'} mode)\n\n${result.content}\n\n*Latency: ${result.latency_ms || 0}ms | Tokens: ${result.tokens_used || 0} | ${result.timestamp}*`;
|
|
397
598
|
} else {
|
|
398
599
|
// Status/detection response
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polydev-ai",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Agentic workflow assistant with CLI integration - get diverse perspectives from multiple LLMs when stuck or need enhanced reasoning",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"lucide-react": "^0.542.0",
|
|
60
60
|
"marked": "^16.2.1",
|
|
61
61
|
"next": "15.0.0",
|
|
62
|
+
"polydev-ai": "^1.2.0",
|
|
62
63
|
"posthog-js": "^1.157.2",
|
|
63
64
|
"react": "^18.3.1",
|
|
64
65
|
"react-dom": "^18.3.1",
|