polydev-ai 1.9.18 → 1.9.20
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 +4 -0
- package/mcp/stdio-wrapper.js +164 -46
- package/package.json +1 -1
package/mcp/manifest.json
CHANGED
|
@@ -98,6 +98,10 @@
|
|
|
98
98
|
"minimum": 30,
|
|
99
99
|
"maximum": 600,
|
|
100
100
|
"default": 300
|
|
101
|
+
},
|
|
102
|
+
"user_token": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "Polydev user token (pd_xxx). Use this in sandboxed environments (Cowork, containers) where browser login is unavailable. Get your token from https://polydev.ai/dashboard"
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
},
|
package/mcp/stdio-wrapper.js
CHANGED
|
@@ -300,6 +300,9 @@ class StdioMCPWrapper {
|
|
|
300
300
|
// Pending session file for surviving restarts
|
|
301
301
|
this.PENDING_SESSION_FILE = path.join(os.homedir(), '.polydev-pending-session');
|
|
302
302
|
|
|
303
|
+
// Sandbox detection (Cowork, containers with limited disk/no browser)
|
|
304
|
+
this._isSandboxed = null; // lazy-detected
|
|
305
|
+
|
|
303
306
|
// MCP client info (set during initialize handshake)
|
|
304
307
|
// Used to detect which IDE is running us and exclude its CLI to avoid recursive calls
|
|
305
308
|
this.clientInfo = null;
|
|
@@ -491,7 +494,18 @@ Token will be saved automatically after login.`
|
|
|
491
494
|
*/
|
|
492
495
|
async handleLoginTool(params, id) {
|
|
493
496
|
const crypto = require('crypto');
|
|
494
|
-
|
|
497
|
+
|
|
498
|
+
// Accept user_token from params (essential for sandboxed environments like Cowork)
|
|
499
|
+
const inlineToken = params?.arguments?.user_token;
|
|
500
|
+
if (inlineToken && (inlineToken.startsWith('pd_') || inlineToken.startsWith('polydev_'))) {
|
|
501
|
+
console.error('[Polydev] Login via inline user_token');
|
|
502
|
+
this.userToken = inlineToken;
|
|
503
|
+
this.isAuthenticated = true;
|
|
504
|
+
process.env.POLYDEV_USER_TOKEN = inlineToken;
|
|
505
|
+
this.saveTokenToFiles(inlineToken);
|
|
506
|
+
return await this.fetchAuthStatusResponse(id);
|
|
507
|
+
}
|
|
508
|
+
|
|
495
509
|
// Check if already authenticated - verify with server first
|
|
496
510
|
if (this.isAuthenticated && this.userToken) {
|
|
497
511
|
console.error('[Polydev] Token exists locally, verifying with server...');
|
|
@@ -525,9 +539,38 @@ To re-login: npx polydev-ai`
|
|
|
525
539
|
this.userToken = null;
|
|
526
540
|
}
|
|
527
541
|
|
|
542
|
+
// In sandboxed environments, don't try browser login — show token-paste instructions
|
|
543
|
+
if (this.isSandboxed()) {
|
|
544
|
+
console.error('[Polydev] Sandbox detected — browser login unavailable');
|
|
545
|
+
return {
|
|
546
|
+
jsonrpc: '2.0',
|
|
547
|
+
id,
|
|
548
|
+
result: {
|
|
549
|
+
content: [{
|
|
550
|
+
type: 'text',
|
|
551
|
+
text: `LOGIN - Sandboxed Environment
|
|
552
|
+
==============================
|
|
553
|
+
|
|
554
|
+
Browser login is not available in this environment (Cowork/container).
|
|
555
|
+
|
|
556
|
+
To authenticate, use one of these methods:
|
|
557
|
+
|
|
558
|
+
**Method 1: Pass token directly**
|
|
559
|
+
1. Get your token from https://polydev.ai/dashboard
|
|
560
|
+
2. Call login with user_token:
|
|
561
|
+
|
|
562
|
+
mcp__plugin_polydev_mcp__login({ user_token: "pd_your_token_here" })
|
|
563
|
+
|
|
564
|
+
**Method 2: Set environment variable**
|
|
565
|
+
Set POLYDEV_USER_TOKEN=pd_your_token before starting the MCP server.`
|
|
566
|
+
}]
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
528
571
|
// Generate unique session ID for polling-based auth
|
|
529
572
|
const sessionId = crypto.randomBytes(32).toString('hex');
|
|
530
|
-
|
|
573
|
+
|
|
531
574
|
try {
|
|
532
575
|
// Create session on server (with retry for transient network issues)
|
|
533
576
|
let createResponse;
|
|
@@ -845,14 +888,16 @@ Still waiting for login. Use /polydev:auth to check status after signing in.`
|
|
|
845
888
|
}
|
|
846
889
|
};
|
|
847
890
|
} catch (error) {
|
|
848
|
-
const
|
|
891
|
+
const errCode = error.cause?.code || error.code || '';
|
|
892
|
+
const cause = errCode ? ` (${errCode})` : (error.cause?.message ? ` (${error.cause.message})` : '');
|
|
893
|
+
console.error(`[Polydev] Re-auth failed: ${error.message}${cause}`);
|
|
849
894
|
return {
|
|
850
895
|
jsonrpc: '2.0',
|
|
851
896
|
id,
|
|
852
897
|
result: {
|
|
853
898
|
content: [{
|
|
854
899
|
type: 'text',
|
|
855
|
-
text: `${reason}\n\nCould not reach server: ${error.message}${cause}\
|
|
900
|
+
text: `${reason}\n\nCould not reach Polydev server: ${error.message}${cause}\n\nTroubleshooting:\n 1. Check your internet connection\n 2. Try: /polydev:login\n 3. Or run in terminal: npx polydev-ai`
|
|
856
901
|
}],
|
|
857
902
|
isError: true
|
|
858
903
|
}
|
|
@@ -934,31 +979,54 @@ Still waiting for login. Use /polydev:auth to check status after signing in.`
|
|
|
934
979
|
* Handle get_auth_status tool - comprehensive status display
|
|
935
980
|
*/
|
|
936
981
|
async handleGetAuthStatus(params, id) {
|
|
982
|
+
// Accept user_token from params (essential for sandboxed environments like Cowork)
|
|
983
|
+
const inlineToken = params?.arguments?.user_token;
|
|
984
|
+
if (inlineToken && (inlineToken.startsWith('pd_') || inlineToken.startsWith('polydev_'))) {
|
|
985
|
+
console.error('[Polydev] Using inline user_token from params');
|
|
986
|
+
this.userToken = inlineToken;
|
|
987
|
+
this.isAuthenticated = true;
|
|
988
|
+
process.env.POLYDEV_USER_TOKEN = inlineToken;
|
|
989
|
+
this.saveTokenToFiles(inlineToken); // safe — handles sandbox gracefully
|
|
990
|
+
}
|
|
991
|
+
|
|
937
992
|
// Try to hot-reload token if not authenticated
|
|
938
993
|
if (!this.isAuthenticated || !this.userToken) {
|
|
939
994
|
this.reloadTokenFromFiles();
|
|
940
995
|
}
|
|
941
|
-
|
|
996
|
+
|
|
942
997
|
if (!this.isAuthenticated || !this.userToken) {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
998
|
+
// No token found — check if we're in a sandbox (Cowork, containers)
|
|
999
|
+
if (this.isSandboxed()) {
|
|
1000
|
+
console.error('[Polydev] No token and sandboxed — showing token-paste instructions');
|
|
1001
|
+
return {
|
|
1002
|
+
jsonrpc: '2.0',
|
|
1003
|
+
id,
|
|
1004
|
+
result: {
|
|
1005
|
+
content: [{
|
|
1006
|
+
type: 'text',
|
|
1007
|
+
text: `POLYDEV STATUS
|
|
950
1008
|
==============
|
|
951
1009
|
|
|
952
|
-
Authentication: Not connected
|
|
1010
|
+
Authentication: Not connected (sandboxed environment detected)
|
|
953
1011
|
|
|
954
|
-
|
|
955
|
-
1. Use the "login" tool (opens browser)
|
|
956
|
-
2. Or run: npx polydev-ai
|
|
1012
|
+
Browser login is not available in this environment.
|
|
957
1013
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1014
|
+
To authenticate, pass your token directly:
|
|
1015
|
+
1. Get your token from https://polydev.ai/dashboard
|
|
1016
|
+
2. Call get_auth_status with user_token parameter:
|
|
1017
|
+
|
|
1018
|
+
mcp__plugin_polydev_mcp__get_auth_status({ user_token: "pd_your_token_here" })
|
|
1019
|
+
|
|
1020
|
+
3. Or set the environment variable before starting:
|
|
1021
|
+
POLYDEV_USER_TOKEN=pd_your_token_here`
|
|
1022
|
+
}]
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// Not sandboxed — auto-trigger browser login
|
|
1028
|
+
console.error('[Polydev] No token found, auto-triggering login flow...');
|
|
1029
|
+
return await this.triggerReAuth(id, 'Not authenticated. Opening browser for login...');
|
|
962
1030
|
}
|
|
963
1031
|
|
|
964
1032
|
try {
|
|
@@ -1053,6 +1121,16 @@ Configure: https://polydev.ai/dashboard/models`
|
|
|
1053
1121
|
return await this.triggerReAuth(id, 'Token invalid or expired.');
|
|
1054
1122
|
}
|
|
1055
1123
|
} catch (error) {
|
|
1124
|
+
// Categorize the error for clearer user messaging
|
|
1125
|
+
const errCode = error.cause?.code || error.code || '';
|
|
1126
|
+
const isNetwork = ['ECONNREFUSED', 'ENOTFOUND', 'ETIMEDOUT', 'ECONNRESET', 'UND_ERR_CONNECT_TIMEOUT', 'FETCH_ERROR'].includes(errCode)
|
|
1127
|
+
|| error.message?.includes('fetch') || error.message?.includes('network');
|
|
1128
|
+
const errorDetail = isNetwork
|
|
1129
|
+
? `Network error: ${error.message}${errCode ? ` (${errCode})` : ''}\nCheck your internet connection and try again.`
|
|
1130
|
+
: `Error: ${error.message}${errCode ? ` (${errCode})` : ''}`;
|
|
1131
|
+
|
|
1132
|
+
console.error(`[Polydev] Auth status check failed: ${error.message} (code: ${errCode})`);
|
|
1133
|
+
|
|
1056
1134
|
return {
|
|
1057
1135
|
jsonrpc: '2.0',
|
|
1058
1136
|
id,
|
|
@@ -1062,9 +1140,12 @@ Configure: https://polydev.ai/dashboard/models`
|
|
|
1062
1140
|
text: `POLYDEV STATUS
|
|
1063
1141
|
==============
|
|
1064
1142
|
|
|
1065
|
-
Could not verify status
|
|
1143
|
+
Could not verify authentication status.
|
|
1144
|
+
|
|
1145
|
+
${errorDetail}
|
|
1066
1146
|
|
|
1067
|
-
|
|
1147
|
+
To retry: /polydev:auth
|
|
1148
|
+
To re-login: /polydev:login`
|
|
1068
1149
|
}],
|
|
1069
1150
|
isError: true
|
|
1070
1151
|
}
|
|
@@ -1072,6 +1153,29 @@ Error: ${error.message}`
|
|
|
1072
1153
|
}
|
|
1073
1154
|
}
|
|
1074
1155
|
|
|
1156
|
+
/**
|
|
1157
|
+
* Detect if running in a sandboxed environment (Cowork, containers, etc.)
|
|
1158
|
+
* where filesystem is read-only/limited and browser is unavailable.
|
|
1159
|
+
* Result is cached after first check.
|
|
1160
|
+
*/
|
|
1161
|
+
isSandboxed() {
|
|
1162
|
+
if (this._isSandboxed !== null) return this._isSandboxed;
|
|
1163
|
+
|
|
1164
|
+
try {
|
|
1165
|
+
// Try writing a test file to home directory
|
|
1166
|
+
const testFile = path.join(os.homedir(), '.polydev-sandbox-test');
|
|
1167
|
+
fs.writeFileSync(testFile, 'test');
|
|
1168
|
+
fs.unlinkSync(testFile);
|
|
1169
|
+
this._isSandboxed = false;
|
|
1170
|
+
} catch (e) {
|
|
1171
|
+
// ENOSPC, EROFS, EACCES all indicate sandbox-like constraints
|
|
1172
|
+
console.error(`[Polydev] Sandbox detected: ${e.code || e.message}`);
|
|
1173
|
+
this._isSandboxed = true;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
return this._isSandboxed;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1075
1179
|
/**
|
|
1076
1180
|
* Open URL in browser - best-in-class cross-platform implementation
|
|
1077
1181
|
* Uses 'open' npm package with fallbacks for reliability in MCP/stdio context
|
|
@@ -1134,27 +1238,39 @@ Error: ${error.message}`
|
|
|
1134
1238
|
* Save token to shell config files
|
|
1135
1239
|
*/
|
|
1136
1240
|
saveTokenToFiles(token) {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1241
|
+
if (this.isSandboxed()) {
|
|
1242
|
+
// In sandbox, just set in-memory — can't write files
|
|
1243
|
+
console.error('[Polydev] Sandbox: skipping file writes, token set in-memory only');
|
|
1244
|
+
process.env.POLYDEV_USER_TOKEN = token;
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1141
1247
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1248
|
+
try {
|
|
1249
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
1250
|
+
const rcFile = shell.includes('zsh') ? path.join(os.homedir(), '.zshrc') :
|
|
1251
|
+
shell.includes('bash') ? path.join(os.homedir(), '.bashrc') :
|
|
1252
|
+
path.join(os.homedir(), '.profile');
|
|
1253
|
+
|
|
1254
|
+
let content = '';
|
|
1255
|
+
try { content = fs.readFileSync(rcFile, 'utf8'); } catch (e) {}
|
|
1144
1256
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1257
|
+
const lines = content.split('\n').filter(line =>
|
|
1258
|
+
!line.trim().startsWith('export POLYDEV_USER_TOKEN') &&
|
|
1259
|
+
!line.trim().startsWith('POLYDEV_USER_TOKEN')
|
|
1260
|
+
);
|
|
1149
1261
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1262
|
+
lines.push(`export POLYDEV_USER_TOKEN="${token}"`);
|
|
1263
|
+
fs.writeFileSync(rcFile, lines.join('\n').replace(/\n+$/, '') + '\n');
|
|
1152
1264
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1265
|
+
// Also save to .polydev.env
|
|
1266
|
+
const envFile = path.join(os.homedir(), '.polydev.env');
|
|
1267
|
+
fs.writeFileSync(envFile, `POLYDEV_USER_TOKEN="${token}"\n`);
|
|
1156
1268
|
|
|
1157
|
-
|
|
1269
|
+
console.error(`[Polydev] Token saved to ${rcFile} and ${envFile}`);
|
|
1270
|
+
} catch (e) {
|
|
1271
|
+
console.error(`[Polydev] Could not write token to files (${e.code || e.message}), set in-memory only`);
|
|
1272
|
+
process.env.POLYDEV_USER_TOKEN = token;
|
|
1273
|
+
}
|
|
1158
1274
|
}
|
|
1159
1275
|
|
|
1160
1276
|
/**
|
|
@@ -1831,7 +1947,7 @@ Error: ${error.message}`
|
|
|
1831
1947
|
// Detect if we should exclude the current IDE's CLI to avoid recursive calls
|
|
1832
1948
|
const excludedCli = this.getExcludedCliForCurrentIDE();
|
|
1833
1949
|
if (excludedCli) {
|
|
1834
|
-
console.error(`[Stdio Wrapper]
|
|
1950
|
+
console.error(`[Stdio Wrapper] [CLI-FIRST] Skipping ${excludedCli} (same as current IDE — would cause recursive call)`);
|
|
1835
1951
|
}
|
|
1836
1952
|
|
|
1837
1953
|
// Build merged provider list: CLIs first, then API-only
|
|
@@ -1901,7 +2017,7 @@ Error: ${error.message}`
|
|
|
1901
2017
|
console.error(`[Stdio Wrapper] Provider breakdown: CLI=${cliProviderEntries.map(p => p.cliId).join(', ') || 'none'}, API-only=${apiOnlyProviders.map(p => p.provider).join(', ') || 'none'}`);
|
|
1902
2018
|
|
|
1903
2019
|
// Run ALL CLI prompts concurrently with fast-collect pattern
|
|
1904
|
-
// Resolves once we have maxPerspectives successes
|
|
2020
|
+
// Resolves once we have maxPerspectives successes OR all complete
|
|
1905
2021
|
if (cliProviderEntries.length > 0) {
|
|
1906
2022
|
const cliPromises = cliProviderEntries.map(async (providerEntry) => {
|
|
1907
2023
|
try {
|
|
@@ -1953,7 +2069,7 @@ Error: ${error.message}`
|
|
|
1953
2069
|
}
|
|
1954
2070
|
});
|
|
1955
2071
|
|
|
1956
|
-
// Fast-collect: resolve once we have maxPerspectives successes OR all complete
|
|
2072
|
+
// Fast-collect: resolve early once we have maxPerspectives successes OR all complete
|
|
1957
2073
|
localResults = await this.collectFirstNSuccesses(cliPromises, maxPerspectives);
|
|
1958
2074
|
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`);
|
|
1959
2075
|
}
|
|
@@ -2664,7 +2780,7 @@ Error: ${error.message}`
|
|
|
2664
2780
|
const staleProviders = [];
|
|
2665
2781
|
for (const [providerId, status] of Object.entries(currentStatus)) {
|
|
2666
2782
|
if (this.isStale(status)) {
|
|
2667
|
-
const minutesOld = Math.floor((
|
|
2783
|
+
const minutesOld = Math.floor((now.getTime() - lastChecked.getTime()) / (1000 * 60));
|
|
2668
2784
|
const timeout = this.getSmartTimeout(status);
|
|
2669
2785
|
staleProviders.push({ providerId, minutesOld, timeout });
|
|
2670
2786
|
}
|
|
@@ -3153,16 +3269,18 @@ Dashboard: https://polydev.ai/dashboard`
|
|
|
3153
3269
|
console.error(`Credits: ${credits} | Tier: ${tier}`);
|
|
3154
3270
|
console.error('─'.repeat(50) + '\n');
|
|
3155
3271
|
} else {
|
|
3156
|
-
|
|
3272
|
+
// Don't clear isAuthenticated here — handleGetAuthStatus has its own
|
|
3273
|
+
// verification and triggerReAuth logic. Clearing here causes a race condition
|
|
3274
|
+
// where startup verification can invalidate auth before the user checks status.
|
|
3157
3275
|
console.error('─'.repeat(50));
|
|
3158
|
-
console.error('Polydev - Token
|
|
3276
|
+
console.error('Polydev - Token may be invalid');
|
|
3159
3277
|
console.error('─'.repeat(50));
|
|
3160
|
-
console.error('
|
|
3278
|
+
console.error('Server returned non-OK. Will re-verify on next auth check.');
|
|
3161
3279
|
console.error('Use the "login" tool or run: npx polydev-ai login');
|
|
3162
3280
|
console.error('─'.repeat(50) + '\n');
|
|
3163
3281
|
}
|
|
3164
3282
|
} catch (error) {
|
|
3165
|
-
console.error('[Polydev] Could not verify auth (offline?)');
|
|
3283
|
+
console.error('[Polydev] Could not verify auth (offline?):', error.message || 'unknown');
|
|
3166
3284
|
}
|
|
3167
3285
|
}
|
|
3168
3286
|
|