polydev-ai 1.8.16 → 1.8.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/mcp/stdio-wrapper.js +137 -42
- package/package.json +1 -1
package/mcp/stdio-wrapper.js
CHANGED
|
@@ -103,6 +103,12 @@ if (writableTmp) {
|
|
|
103
103
|
/**
|
|
104
104
|
* Clean CLI response by removing metadata and debug info
|
|
105
105
|
* Strips: provider info, approval, sandbox, reasoning, session id, MCP errors, etc.
|
|
106
|
+
*
|
|
107
|
+
* Codex CLI output structure:
|
|
108
|
+
* [metadata] → user → [echoed prompt] → thinking → [status] → codex → [RESPONSE]
|
|
109
|
+
*
|
|
110
|
+
* Claude Code output structure:
|
|
111
|
+
* [may include JSON or plain text response]
|
|
106
112
|
*/
|
|
107
113
|
function cleanCliResponse(content) {
|
|
108
114
|
if (!content || typeof content !== 'string') {
|
|
@@ -111,57 +117,108 @@ function cleanCliResponse(content) {
|
|
|
111
117
|
|
|
112
118
|
const lines = content.split('\n');
|
|
113
119
|
const cleanedLines = [];
|
|
114
|
-
let skipUntilContent = true;
|
|
115
|
-
let inThinkingSection = false;
|
|
116
120
|
|
|
117
|
-
for
|
|
121
|
+
// State machine for Codex CLI output parsing
|
|
122
|
+
// States: 'metadata' | 'user_section' | 'thinking_section' | 'response'
|
|
123
|
+
let state = 'metadata';
|
|
124
|
+
let foundCodexMarker = false;
|
|
125
|
+
|
|
126
|
+
for (let i = 0; i < lines.length; i++) {
|
|
127
|
+
const line = lines[i];
|
|
118
128
|
const trimmed = line.trim();
|
|
119
129
|
|
|
120
|
-
//
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
// Always skip MCP errors anywhere
|
|
131
|
+
if (trimmed.startsWith('ERROR: MCP client for')) continue;
|
|
132
|
+
if (trimmed.includes('failed to start') && trimmed.includes('MCP')) continue;
|
|
133
|
+
if (trimmed.includes('handshake') && trimmed.includes('failed')) continue;
|
|
134
|
+
if (trimmed.includes('connection closed')) continue;
|
|
135
|
+
|
|
136
|
+
// State: metadata - skip until we hit a section marker
|
|
137
|
+
if (state === 'metadata') {
|
|
125
138
|
// Skip known metadata patterns
|
|
139
|
+
if (!trimmed) continue;
|
|
126
140
|
if (trimmed.startsWith('provider:')) continue;
|
|
127
141
|
if (trimmed.startsWith('approval:')) continue;
|
|
128
142
|
if (trimmed.startsWith('sandbox:')) continue;
|
|
129
143
|
if (trimmed.startsWith('reasoning effort:')) continue;
|
|
130
144
|
if (trimmed.startsWith('reasoning summaries:')) continue;
|
|
131
145
|
if (trimmed.startsWith('session id:')) continue;
|
|
132
|
-
if (trimmed.match(/^-{4,}$/)) continue; //
|
|
133
|
-
|
|
134
|
-
|
|
146
|
+
if (trimmed.match(/^-{4,}$/)) continue; // Separator lines
|
|
147
|
+
|
|
148
|
+
// Transition to user section
|
|
149
|
+
if (trimmed === 'user') {
|
|
150
|
+
state = 'user_section';
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
135
153
|
|
|
136
|
-
//
|
|
137
|
-
if (trimmed
|
|
138
|
-
|
|
154
|
+
// If we hit 'codex' directly (no user section), go to response
|
|
155
|
+
if (trimmed === 'codex') {
|
|
156
|
+
state = 'response';
|
|
157
|
+
foundCodexMarker = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If we hit 'assistant' (Claude Code), this might be actual content
|
|
162
|
+
if (trimmed === 'assistant') {
|
|
163
|
+
state = 'response';
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// If no Codex markers found and we have content, it might be Claude Code output
|
|
168
|
+
// Check if it looks like actual content (not metadata)
|
|
169
|
+
if (!trimmed.includes(':') || trimmed.length > 100) {
|
|
170
|
+
state = 'response';
|
|
171
|
+
cleanedLines.push(line);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
139
174
|
|
|
140
|
-
|
|
141
|
-
skipUntilContent = false;
|
|
175
|
+
continue;
|
|
142
176
|
}
|
|
143
177
|
|
|
144
|
-
//
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
178
|
+
// State: user_section - skip echoed prompt until thinking/codex
|
|
179
|
+
if (state === 'user_section') {
|
|
180
|
+
if (trimmed === 'thinking') {
|
|
181
|
+
state = 'thinking_section';
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (trimmed === 'codex') {
|
|
185
|
+
state = 'response';
|
|
186
|
+
foundCodexMarker = true;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
// Skip everything in user section (echoed prompt, errors)
|
|
190
|
+
continue;
|
|
148
191
|
}
|
|
149
192
|
|
|
150
|
-
//
|
|
151
|
-
if (
|
|
152
|
-
|
|
193
|
+
// State: thinking_section - skip until codex marker
|
|
194
|
+
if (state === 'thinking_section') {
|
|
195
|
+
if (trimmed === 'codex') {
|
|
196
|
+
state = 'response';
|
|
197
|
+
foundCodexMarker = true;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
// Skip thinking content (bold status messages, reasoning)
|
|
153
201
|
continue;
|
|
154
202
|
}
|
|
155
203
|
|
|
156
|
-
//
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
204
|
+
// State: response - keep actual response content
|
|
205
|
+
if (state === 'response') {
|
|
206
|
+
// Skip footer patterns
|
|
207
|
+
if (trimmed === 'tokens used') continue;
|
|
208
|
+
if (trimmed.match(/^[\d,]+$/) && i === lines.length - 1) continue; // Token count at end
|
|
209
|
+
|
|
210
|
+
// Skip any remaining bold status markers
|
|
211
|
+
if (trimmed.match(/^\*\*.*\*\*$/)) continue;
|
|
212
|
+
|
|
213
|
+
// Keep this line
|
|
214
|
+
cleanedLines.push(line);
|
|
215
|
+
}
|
|
162
216
|
}
|
|
163
217
|
|
|
164
|
-
// Trim trailing empty lines
|
|
218
|
+
// Trim leading/trailing empty lines
|
|
219
|
+
while (cleanedLines.length > 0 && !cleanedLines[0].trim()) {
|
|
220
|
+
cleanedLines.shift();
|
|
221
|
+
}
|
|
165
222
|
while (cleanedLines.length > 0 && !cleanedLines[cleanedLines.length - 1].trim()) {
|
|
166
223
|
cleanedLines.pop();
|
|
167
224
|
}
|
|
@@ -188,6 +245,7 @@ class StdioMCPWrapper {
|
|
|
188
245
|
|
|
189
246
|
// Cache for user model preferences (provider -> model)
|
|
190
247
|
this.userModelPreferences = null;
|
|
248
|
+
this.perspectivesPerMessage = 2; // Default to 2 perspectives
|
|
191
249
|
this.modelPreferencesCacheTime = null;
|
|
192
250
|
this.MODEL_PREFERENCES_CACHE_TTL = 5 * 60 * 1000; // 5 minutes cache
|
|
193
251
|
}
|
|
@@ -507,6 +565,7 @@ class StdioMCPWrapper {
|
|
|
507
565
|
|
|
508
566
|
/**
|
|
509
567
|
* Local CLI prompt sending with ALL available CLIs + remote perspectives
|
|
568
|
+
* Respects user's perspectives_per_message setting for total perspectives
|
|
510
569
|
*/
|
|
511
570
|
async localSendCliPrompt(args) {
|
|
512
571
|
console.error(`[Stdio Wrapper] Local CLI prompt sending with perspectives`);
|
|
@@ -528,6 +587,7 @@ class StdioMCPWrapper {
|
|
|
528
587
|
const gracefulTimeout = Math.min(timeout_ms, 600000);
|
|
529
588
|
|
|
530
589
|
// Fetch user's model preferences (cached, non-blocking on failure)
|
|
590
|
+
// This also fetches perspectivesPerMessage setting
|
|
531
591
|
let modelPreferences = {};
|
|
532
592
|
try {
|
|
533
593
|
modelPreferences = await this.fetchUserModelPreferences();
|
|
@@ -535,6 +595,10 @@ class StdioMCPWrapper {
|
|
|
535
595
|
console.error('[Stdio Wrapper] Model preferences fetch failed (will use CLI defaults):', prefError.message);
|
|
536
596
|
}
|
|
537
597
|
|
|
598
|
+
// Get the user's perspectives_per_message setting (default 2)
|
|
599
|
+
const maxPerspectives = this.perspectivesPerMessage || 2;
|
|
600
|
+
console.error(`[Stdio Wrapper] Max perspectives per message: ${maxPerspectives}`);
|
|
601
|
+
|
|
538
602
|
let localResults = [];
|
|
539
603
|
|
|
540
604
|
if (provider_id) {
|
|
@@ -549,15 +613,18 @@ class StdioMCPWrapper {
|
|
|
549
613
|
const result = await this.cliManager.sendCliPrompt(provider_id, prompt, mode, gracefulTimeout, model);
|
|
550
614
|
localResults = [{ provider_id, ...result }];
|
|
551
615
|
} else {
|
|
552
|
-
// No specific provider - use
|
|
553
|
-
console.error(`[Stdio Wrapper] Using
|
|
554
|
-
const
|
|
616
|
+
// No specific provider - use available local CLIs up to maxPerspectives limit
|
|
617
|
+
console.error(`[Stdio Wrapper] Using available local CLIs (max: ${maxPerspectives})`);
|
|
618
|
+
const allAvailableProviders = await this.getAllAvailableProviders();
|
|
619
|
+
|
|
620
|
+
// Limit to maxPerspectives
|
|
621
|
+
const availableProviders = allAvailableProviders.slice(0, maxPerspectives);
|
|
555
622
|
|
|
556
623
|
if (availableProviders.length === 0) {
|
|
557
624
|
console.error(`[Stdio Wrapper] No local CLIs available, will use remote perspectives only`);
|
|
558
625
|
localResults = [];
|
|
559
626
|
} else {
|
|
560
|
-
console.error(`[Stdio Wrapper] Found ${
|
|
627
|
+
console.error(`[Stdio Wrapper] Found ${allAvailableProviders.length} available CLIs, using ${availableProviders.length}: ${availableProviders.join(', ')}`);
|
|
561
628
|
|
|
562
629
|
// Run all CLI prompts concurrently
|
|
563
630
|
const cliPromises = availableProviders.map(async (providerId) => {
|
|
@@ -588,8 +655,25 @@ class StdioMCPWrapper {
|
|
|
588
655
|
console.error('[Stdio Wrapper] CLI results reporting failed (non-critical):', err.message);
|
|
589
656
|
});
|
|
590
657
|
|
|
591
|
-
//
|
|
592
|
-
const
|
|
658
|
+
// Calculate how many successful local perspectives we got
|
|
659
|
+
const successfulLocalCount = localResults.filter(r => r.success).length;
|
|
660
|
+
const remainingPerspectives = maxPerspectives - successfulLocalCount;
|
|
661
|
+
|
|
662
|
+
// Get remote perspectives only if we need more perspectives to reach maxPerspectives
|
|
663
|
+
let perspectivesResult;
|
|
664
|
+
if (remainingPerspectives > 0) {
|
|
665
|
+
console.error(`[Stdio Wrapper] Need ${remainingPerspectives} more perspectives from remote`);
|
|
666
|
+
perspectivesResult = await this.callPerspectivesForCli(args, localResults, remainingPerspectives);
|
|
667
|
+
} else {
|
|
668
|
+
console.error(`[Stdio Wrapper] Already have ${successfulLocalCount} perspectives, skipping remote call`);
|
|
669
|
+
perspectivesResult = {
|
|
670
|
+
success: true,
|
|
671
|
+
content: '',
|
|
672
|
+
skipped: true,
|
|
673
|
+
reason: `Already have ${successfulLocalCount} perspectives (max: ${maxPerspectives})`,
|
|
674
|
+
timestamp: new Date().toISOString()
|
|
675
|
+
};
|
|
676
|
+
}
|
|
593
677
|
|
|
594
678
|
// Record usage for all CLI responses
|
|
595
679
|
for (const localResult of localResults) {
|
|
@@ -725,8 +809,11 @@ class StdioMCPWrapper {
|
|
|
725
809
|
/**
|
|
726
810
|
* Call remote perspectives for CLI prompts
|
|
727
811
|
* Only calls remote APIs for providers NOT covered by successful local CLIs
|
|
812
|
+
* @param {Object} args - Original request arguments
|
|
813
|
+
* @param {Array} localResults - Results from local CLIs
|
|
814
|
+
* @param {number} maxPerspectives - Maximum number of remote perspectives to fetch
|
|
728
815
|
*/
|
|
729
|
-
async callPerspectivesForCli(args, localResults) {
|
|
816
|
+
async callPerspectivesForCli(args, localResults, maxPerspectives = 2) {
|
|
730
817
|
// Determine which providers succeeded locally
|
|
731
818
|
const successfulLocalProviders = localResults
|
|
732
819
|
.filter(r => r.success)
|
|
@@ -743,20 +830,21 @@ class StdioMCPWrapper {
|
|
|
743
830
|
.map(cli => cliToApiProvider[cli])
|
|
744
831
|
.filter(Boolean);
|
|
745
832
|
|
|
746
|
-
// If all major providers are covered locally, skip remote call
|
|
747
|
-
if (
|
|
833
|
+
// If all major providers are covered locally OR we don't need more perspectives, skip remote call
|
|
834
|
+
if (maxPerspectives <= 0 ||
|
|
835
|
+
excludeProviders.length >= 3 ||
|
|
748
836
|
(excludeProviders.includes('anthropic') && excludeProviders.includes('openai') && excludeProviders.includes('google'))) {
|
|
749
|
-
console.error(`[Stdio Wrapper] All providers covered by local CLIs, skipping remote perspectives`);
|
|
837
|
+
console.error(`[Stdio Wrapper] All providers covered by local CLIs or max perspectives reached, skipping remote perspectives`);
|
|
750
838
|
return {
|
|
751
839
|
success: true,
|
|
752
840
|
content: '',
|
|
753
841
|
skipped: true,
|
|
754
|
-
reason: 'All providers covered by local CLIs',
|
|
842
|
+
reason: 'All providers covered by local CLIs or max perspectives reached',
|
|
755
843
|
timestamp: new Date().toISOString()
|
|
756
844
|
};
|
|
757
845
|
}
|
|
758
846
|
|
|
759
|
-
console.error(`[Stdio Wrapper] Calling remote perspectives (excluding: ${excludeProviders.join(', ') || 'none'})`);
|
|
847
|
+
console.error(`[Stdio Wrapper] Calling remote perspectives (excluding: ${excludeProviders.join(', ') || 'none'}, max: ${maxPerspectives})`);
|
|
760
848
|
|
|
761
849
|
// Format CLI responses for logging on the server
|
|
762
850
|
const cliResponses = localResults.map(result => ({
|
|
@@ -782,6 +870,8 @@ class StdioMCPWrapper {
|
|
|
782
870
|
exclude_providers: excludeProviders,
|
|
783
871
|
// Pass CLI responses for dashboard logging
|
|
784
872
|
cli_responses: cliResponses,
|
|
873
|
+
// Limit remote perspectives to what we need
|
|
874
|
+
max_perspectives: maxPerspectives,
|
|
785
875
|
project_memory: 'none',
|
|
786
876
|
temperature: 0.7,
|
|
787
877
|
max_tokens: 20000
|
|
@@ -1250,6 +1340,7 @@ class StdioMCPWrapper {
|
|
|
1250
1340
|
/**
|
|
1251
1341
|
* Fetch user's model preferences from API keys
|
|
1252
1342
|
* Returns a map of CLI provider -> default_model
|
|
1343
|
+
* Also fetches and caches perspectivesPerMessage setting
|
|
1253
1344
|
*/
|
|
1254
1345
|
async fetchUserModelPreferences() {
|
|
1255
1346
|
// Check cache first
|
|
@@ -1285,7 +1376,11 @@ class StdioMCPWrapper {
|
|
|
1285
1376
|
this.userModelPreferences = result.modelPreferences;
|
|
1286
1377
|
this.modelPreferencesCacheTime = Date.now();
|
|
1287
1378
|
|
|
1379
|
+
// Also cache perspectives_per_message setting (default 2)
|
|
1380
|
+
this.perspectivesPerMessage = result.perspectivesPerMessage || 2;
|
|
1381
|
+
|
|
1288
1382
|
console.error('[Stdio Wrapper] Model preferences loaded:', JSON.stringify(result.modelPreferences));
|
|
1383
|
+
console.error('[Stdio Wrapper] Perspectives per message:', this.perspectivesPerMessage);
|
|
1289
1384
|
return result.modelPreferences;
|
|
1290
1385
|
} else {
|
|
1291
1386
|
console.error('[Stdio Wrapper] No model preferences in response');
|
package/package.json
CHANGED