fix(extension): SDK LS 대소문자 매칭 버그 수정 — fixLSConnection() 추가 (멀티프로젝트 신호 누락 해결)
This commit is contained in:
@@ -214,6 +214,13 @@ async function initSDK(context: vscode.ExtensionContext): Promise<boolean> {
|
||||
sdk = new AntigravitySDK(context);
|
||||
await sdk.initialize();
|
||||
console.log('Gravity Bridge: ✅ SDK initialized');
|
||||
|
||||
// ── FIX: SDK's _findLSProcess() uses case-sensitive String.includes() ──
|
||||
// workspace_id in LS process has 'Desktop' (capital D), but SDK hint
|
||||
// generates 'desktop' (lowercase) → match fails → connects to WRONG LS.
|
||||
// Re-discover the correct LS using case-insensitive workspace_id matching.
|
||||
await fixLSConnection();
|
||||
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
console.log(`Gravity Bridge: SDK init failed: ${err.message}`);
|
||||
@@ -221,6 +228,158 @@ async function initSDK(context: vscode.ExtensionContext): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix SDK's LS connection by finding the correct language_server process
|
||||
* for this workspace using case-insensitive matching.
|
||||
*
|
||||
* SDK bug: _findLSProcess() compares workspaceHint via JS String.includes()
|
||||
* which is case-sensitive. workspace_id in process args has original casing
|
||||
* (e.g., file_c_3A_Users_Certes_Desktop_variet_agent) but SDK hint is
|
||||
* lowercased (desktop_variet_agent) → no match → falls back to first LS
|
||||
* found (wrong workspace).
|
||||
*/
|
||||
async function fixLSConnection(): Promise<void> {
|
||||
if (!sdk?.ls) return;
|
||||
try {
|
||||
const folders = vscode.workspace.workspaceFolders;
|
||||
if (!folders || folders.length === 0) return;
|
||||
|
||||
// Generate the workspace hint the same way SDK does, but we'll match case-insensitively
|
||||
const folder = folders[0].uri.fsPath;
|
||||
const parts = folder.replace(/\\/g, '/').split('/');
|
||||
const hint = parts.slice(-2).join('_').replace(/[-.\s]/g, '_').toLowerCase();
|
||||
|
||||
if (!hint) return;
|
||||
|
||||
// Find all language_server processes with csrf_token
|
||||
const { exec } = cp;
|
||||
const { promisify } = require('util');
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
let output: string;
|
||||
try {
|
||||
const psScript = `Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match 'language_server' -and $_.CommandLine -match 'csrf_token' } | ForEach-Object { $_.ProcessId.ToString() + '|' + $_.CommandLine }`;
|
||||
const encoded = Buffer.from(psScript, 'utf16le').toString('base64');
|
||||
const result = await execAsync(
|
||||
`powershell.exe -NoProfile -EncodedCommand ${encoded}`,
|
||||
{ encoding: 'utf8', timeout: 15000, windowsHide: true }
|
||||
);
|
||||
output = result.stdout;
|
||||
} catch {
|
||||
return; // Can't discover processes — leave SDK's choice
|
||||
}
|
||||
|
||||
const lines = output.split('\n').filter((l: string) => l.trim().length > 0);
|
||||
if (lines.length <= 1) return; // Only one LS — no ambiguity
|
||||
|
||||
// Find the line whose workspace_id matches our workspace (case-insensitive)
|
||||
let matchedLine: string | null = null;
|
||||
for (const line of lines) {
|
||||
const lower = line.toLowerCase();
|
||||
// Match workspace_id arg against our hint
|
||||
const wsMatch = line.match(/--workspace_id[= ](\S+)/i);
|
||||
if (wsMatch) {
|
||||
const wsid = wsMatch[1].toLowerCase();
|
||||
if (wsid.includes(hint)) {
|
||||
matchedLine = line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchedLine) {
|
||||
logToFile(`[LS-FIX] No LS process matched hint="${hint}" (${lines.length} processes)`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract port and csrf_token from matched line
|
||||
const csrfMatch = matchedLine.match(/--csrf_token[= ](\S+)/);
|
||||
const extPortMatch = matchedLine.match(/--extension_server_port[= ](\d+)/);
|
||||
const pidMatch = matchedLine.split('|')[0]?.trim();
|
||||
|
||||
if (!csrfMatch || !extPortMatch) {
|
||||
logToFile(`[LS-FIX] Matched LS but missing csrf/port args`);
|
||||
return;
|
||||
}
|
||||
|
||||
const csrfToken = csrfMatch[1];
|
||||
const extPort = parseInt(extPortMatch[1], 10);
|
||||
const pid = parseInt(pidMatch || '0', 10);
|
||||
|
||||
// Check if SDK already connected to this LS
|
||||
if (sdk.ls.port === extPort) {
|
||||
logToFile(`[LS-FIX] SDK already on correct LS port=${extPort}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find ConnectRPC port via netstat (same as SDK logic)
|
||||
let netstatOutput: string;
|
||||
try {
|
||||
const result = await execAsync(
|
||||
`netstat -aon | findstr "LISTENING" | findstr "${pid}"`,
|
||||
{ encoding: 'utf8', timeout: 5000, windowsHide: true }
|
||||
);
|
||||
netstatOutput = result.stdout;
|
||||
} catch {
|
||||
// Netstat failed — try extension_server_port as fallback
|
||||
logToFile(`[LS-FIX] netstat failed, using ext_port=${extPort} for PID=${pid}`);
|
||||
sdk.ls.setConnection(extPort, csrfToken, false);
|
||||
logToFile(`[LS-FIX] ✅ Reconnected to correct LS: port=${extPort} hint="${hint}" PID=${pid}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const portMatches = netstatOutput.matchAll(/127\.0\.0\.1:(\d+)/g);
|
||||
const ports: number[] = [];
|
||||
for (const m of portMatches) {
|
||||
const p = parseInt(m[1], 10);
|
||||
if (p !== extPort && !ports.includes(p)) {
|
||||
ports.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
// Try each port — prefer HTTPS, fall back to HTTP
|
||||
const httpModule = require('http');
|
||||
const httpsModule = require('https');
|
||||
|
||||
for (const useTls of [true, false]) {
|
||||
const mod = useTls ? httpsModule : httpModule;
|
||||
const proto = useTls ? 'https' : 'http';
|
||||
for (const port of ports) {
|
||||
try {
|
||||
const ok = await new Promise<boolean>((resolve) => {
|
||||
const req = mod.request(
|
||||
`${proto}://127.0.0.1:${port}/exa.language_server_pb.LanguageServerService/GetUserStatus`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Content-Length': 2 },
|
||||
rejectUnauthorized: false,
|
||||
timeout: 2000,
|
||||
},
|
||||
(res: any) => resolve(res.statusCode === 200 || res.statusCode === 401)
|
||||
);
|
||||
req.on('error', () => resolve(false));
|
||||
req.on('timeout', () => { req.destroy(); resolve(false); });
|
||||
req.write('{}');
|
||||
req.end();
|
||||
});
|
||||
|
||||
if (ok) {
|
||||
sdk.ls.setConnection(port, csrfToken, useTls);
|
||||
logToFile(`[LS-FIX] ✅ Reconnected to correct LS: port=${port} ${proto} hint="${hint}" PID=${pid}`);
|
||||
return;
|
||||
}
|
||||
} catch { /* try next */ }
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: use extension_server_port
|
||||
sdk.ls.setConnection(extPort, csrfToken, false);
|
||||
logToFile(`[LS-FIX] ✅ Reconnected via ext_port=${extPort} hint="${hint}" PID=${pid}`);
|
||||
} catch (err: any) {
|
||||
logToFile(`[LS-FIX] error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Approval Observer via SDK IntegrationManager ───
|
||||
|
||||
async function setupApprovalObserver() {
|
||||
|
||||
Reference in New Issue
Block a user