feat: LS ConnectRPC bridge for AI response relay to Discord
This commit is contained in:
Binary file not shown.
@@ -118,6 +118,250 @@ function activate(context) {
|
|||||||
context.subscriptions.push(statusBar);
|
context.subscriptions.push(statusBar);
|
||||||
// Register commands
|
// Register commands
|
||||||
context.subscriptions.push(vscode.commands.registerCommand('gravityBridge.start', startBridge), vscode.commands.registerCommand('gravityBridge.stop', stopBridge), vscode.commands.registerCommand('gravityBridge.connect', connectSession), vscode.commands.registerCommand('gravityBridge.approve', () => handleManualAction(true)), vscode.commands.registerCommand('gravityBridge.reject', () => handleManualAction(false)));
|
context.subscriptions.push(vscode.commands.registerCommand('gravityBridge.start', startBridge), vscode.commands.registerCommand('gravityBridge.stop', stopBridge), vscode.commands.registerCommand('gravityBridge.connect', connectSession), vscode.commands.registerCommand('gravityBridge.approve', () => handleManualAction(true)), vscode.commands.registerCommand('gravityBridge.reject', () => handleManualAction(false)));
|
||||||
|
// === LS ConnectRPC Bridge: Relay AI responses to Discord ===
|
||||||
|
let lsPort = null;
|
||||||
|
let lsCsrf = '';
|
||||||
|
let lsPid = null;
|
||||||
|
let lastStepIndex = {}; // cascadeId → last known step index
|
||||||
|
async function discoverLS() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// Phase 1: Find LS process → PID + CSRF token
|
||||||
|
cp.exec('powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object {$_.Name -eq \'language_server_exe.exe\' -or ($_.CommandLine -and $_.CommandLine -like \'*language_server*\' -and $_.CommandLine -notlike \'*powershell*\')} | Select-Object ProcessId, CommandLine | ConvertTo-Json"', { maxBuffer: 2 * 1024 * 1024 }, (err, stdout) => {
|
||||||
|
if (err || !stdout.trim()) {
|
||||||
|
console.log(`Gravity Bridge: [LS] process not found`);
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let procs = JSON.parse(stdout.trim());
|
||||||
|
if (!Array.isArray(procs)) {
|
||||||
|
procs = [procs];
|
||||||
|
}
|
||||||
|
// Find a process with csrf_token in command line
|
||||||
|
for (const proc of procs) {
|
||||||
|
const cmd = proc.CommandLine || '';
|
||||||
|
const csrfM = cmd.match(/--csrf_token[= ]([^\s"]+)/);
|
||||||
|
if (csrfM) {
|
||||||
|
lsPid = proc.ProcessId;
|
||||||
|
lsCsrf = csrfM[1];
|
||||||
|
console.log(`Gravity Bridge: [LS] PID=${lsPid}, CSRF=${lsCsrf.substring(0, 12)}...`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(`Gravity Bridge: [LS] parse error: ${e}`);
|
||||||
|
}
|
||||||
|
if (!lsPid || !lsCsrf) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Phase 2: netstat → find LS listening ports
|
||||||
|
cp.exec(`netstat -ano | findstr "LISTENING" | findstr " ${lsPid}"`, { maxBuffer: 512 * 1024 }, async (err2, stdout2) => {
|
||||||
|
if (err2 || !stdout2.trim()) {
|
||||||
|
console.log(`Gravity Bridge: [LS] no listening ports found for PID ${lsPid}`);
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Parse ports
|
||||||
|
const ports = [];
|
||||||
|
for (const line of stdout2.split('\n')) {
|
||||||
|
const m = line.match(/:(\d+)\s+.*LISTENING/);
|
||||||
|
if (m) {
|
||||||
|
ports.push(parseInt(m[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const uniquePorts = [...new Set(ports)].sort((a, b) => a - b);
|
||||||
|
console.log(`Gravity Bridge: [LS] ports for PID ${lsPid}: ${uniquePorts.join(', ')}`);
|
||||||
|
// Try ConnectRPC probe on each port (HTTP first, then HTTPS)
|
||||||
|
for (const port of uniquePorts) {
|
||||||
|
const ok = await probeLSPort(port);
|
||||||
|
if (ok) {
|
||||||
|
lsPort = port;
|
||||||
|
console.log(`Gravity Bridge: [LS] ✅ ConnectRPC active on port ${port}`);
|
||||||
|
resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`Gravity Bridge: [LS] no ConnectRPC port responded`);
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function probeLSPort(port) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const http = require('http');
|
||||||
|
const https = require('https');
|
||||||
|
// Try HTTP first (extension_server uses HTTP)
|
||||||
|
const tryProto = (proto, useTls) => {
|
||||||
|
const req = proto.request({
|
||||||
|
hostname: '127.0.0.1',
|
||||||
|
port,
|
||||||
|
path: '/exa.language_server_pb.LanguageServerService/GetTrajectoryDescriptions',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-codeium-csrf-token': lsCsrf,
|
||||||
|
},
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
timeout: 2000,
|
||||||
|
}, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => { data += chunk; });
|
||||||
|
res.on('end', () => {
|
||||||
|
// Any response (even error) means ConnectRPC is here
|
||||||
|
console.log(`Gravity Bridge: [LS] port ${port} (${useTls ? 'https' : 'http'}) status=${res.statusCode} body=${data.substring(0, 200)}`);
|
||||||
|
resolve(res.statusCode !== 404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', () => {
|
||||||
|
if (!useTls) {
|
||||||
|
// Try HTTPS
|
||||||
|
tryProto(https, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
req.on('timeout', () => { req.destroy(); resolve(false); });
|
||||||
|
req.write('{}');
|
||||||
|
req.end();
|
||||||
|
};
|
||||||
|
tryProto(http, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function lsRPC(method, payload = {}) {
|
||||||
|
if (!lsPort || !lsCsrf) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const http = require('http');
|
||||||
|
const https = require('https');
|
||||||
|
const proto = lsPort > 40000 ? https : http; // heuristic
|
||||||
|
const body = JSON.stringify(payload);
|
||||||
|
const req = proto.request({
|
||||||
|
hostname: '127.0.0.1',
|
||||||
|
port: lsPort,
|
||||||
|
path: `/exa.language_server_pb.LanguageServerService/${method}`,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-codeium-csrf-token': lsCsrf,
|
||||||
|
'Content-Length': Buffer.byteLength(body),
|
||||||
|
},
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
timeout: 5000,
|
||||||
|
}, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => { data += chunk; });
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
resolve(JSON.parse(data));
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', (e) => {
|
||||||
|
console.log(`Gravity Bridge: [LS RPC] ${method} error: ${e.message}`);
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
req.on('timeout', () => { req.destroy(); resolve(null); });
|
||||||
|
req.write(body);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function pollConversations() {
|
||||||
|
if (!lsPort) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Get trajectory descriptions — lightweight list of all conversations
|
||||||
|
const result = await lsRPC('GetTrajectoryDescriptions');
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Debug: log structure on first call
|
||||||
|
if (Object.keys(lastStepIndex).length === 0) {
|
||||||
|
console.log(`Gravity Bridge: [LS] trajectories: ${JSON.stringify(result).substring(0, 500)}`);
|
||||||
|
}
|
||||||
|
const trajectories = result.trajectories || result.trajectory_descriptions || [];
|
||||||
|
if (!Array.isArray(trajectories)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const traj of trajectories) {
|
||||||
|
const id = traj.googleAgentId || traj.google_agent_id || traj.id || '';
|
||||||
|
const stepIdx = traj.lastStepIndex ?? traj.last_step_index ?? traj.step_count ?? 0;
|
||||||
|
const prev = lastStepIndex[id];
|
||||||
|
if (prev !== undefined && stepIdx > prev) {
|
||||||
|
// New steps! Fetch full trajectory to get AI response
|
||||||
|
console.log(`Gravity Bridge: [LS] ${id.substring(0, 8)} new steps: ${prev} → ${stepIdx}`);
|
||||||
|
const full = await lsRPC('GetCascadeTrajectory', {
|
||||||
|
googleAgentId: id,
|
||||||
|
trajectoryId: traj.trajectoryId || traj.trajectory_id || '',
|
||||||
|
});
|
||||||
|
if (full) {
|
||||||
|
extractAndRelay(full, prev, stepIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastStepIndex[id] = stepIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(`Gravity Bridge: [LS poll] error: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function extractAndRelay(trajectory, fromStep, toStep) {
|
||||||
|
// Extract PlannerResponse or assistant messages from trajectory steps
|
||||||
|
const steps = trajectory.steps || trajectory.cortex_steps || [];
|
||||||
|
const messages = [];
|
||||||
|
for (const step of steps) {
|
||||||
|
const idx = step.index ?? step.step_index ?? 0;
|
||||||
|
if (idx <= fromStep) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const type = step.type || step.step_type || '';
|
||||||
|
const content = step.content || step.summary || step.text || '';
|
||||||
|
// PlannerResponse = AI's text output to user
|
||||||
|
if ((type === 'PlannerResponse' || type === 'planner_response') && content) {
|
||||||
|
messages.push(content);
|
||||||
|
}
|
||||||
|
// Also capture user-facing messages
|
||||||
|
if (step.data?.content && typeof step.data.content === 'string') {
|
||||||
|
messages.push(step.data.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback: if no detailed steps, try messages array
|
||||||
|
if (messages.length === 0) {
|
||||||
|
const msgs = trajectory.messages || trajectory.chat_messages || [];
|
||||||
|
for (const msg of msgs) {
|
||||||
|
if (msg.role === 'assistant' && msg.content) {
|
||||||
|
messages.push(msg.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (messages.length > 0) {
|
||||||
|
const combined = messages.join('\n\n---\n\n');
|
||||||
|
writeChatSnapshot(combined);
|
||||||
|
console.log(`Gravity Bridge: [LS] relayed ${messages.length} response(s) to Discord`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Start LS bridge after a delay
|
||||||
|
setTimeout(async () => {
|
||||||
|
const found = await discoverLS();
|
||||||
|
if (found) {
|
||||||
|
console.log(`Gravity Bridge: [LS] bridge active — polling every 5s`);
|
||||||
|
// Initialize step counts
|
||||||
|
await pollConversations();
|
||||||
|
// Start polling loop
|
||||||
|
setInterval(pollConversations, 5000);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`Gravity Bridge: [LS] bridge NOT available — AI responses won't relay`);
|
||||||
|
}
|
||||||
|
}, 8000);
|
||||||
// Chat document change listener — captures AI text responses
|
// Chat document change listener — captures AI text responses
|
||||||
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument((event) => {
|
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument((event) => {
|
||||||
handleChatDocumentChange(event);
|
handleChatDocumentChange(event);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -97,79 +97,256 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
vscode.commands.registerCommand('gravityBridge.reject', () => handleManualAction(false)),
|
vscode.commands.registerCommand('gravityBridge.reject', () => handleManualAction(false)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// === PROBE: Find where AI conversation text lives ===
|
// === LS ConnectRPC Bridge: Relay AI responses to Discord ===
|
||||||
setTimeout(async () => {
|
let lsPort: number | null = null;
|
||||||
console.log('Gravity Bridge: === PROBE START ===');
|
let lsCsrf: string = '';
|
||||||
|
let lsPid: number | null = null;
|
||||||
|
let lastStepIndex: Record<string, number> = {}; // cascadeId → last known step index
|
||||||
|
|
||||||
// Probe 1: getManagerTrace — may contain step-level data
|
async function discoverLS(): Promise<boolean> {
|
||||||
try {
|
return new Promise((resolve) => {
|
||||||
const trace: any = await vscode.commands.executeCommand('antigravity.getManagerTrace');
|
// Phase 1: Find LS process → PID + CSRF token
|
||||||
console.log(`Gravity Bridge: [getManagerTrace] type=${typeof trace}`);
|
cp.exec(
|
||||||
if (trace) {
|
'powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object {$_.Name -eq \'language_server_exe.exe\' -or ($_.CommandLine -and $_.CommandLine -like \'*language_server*\' -and $_.CommandLine -notlike \'*powershell*\')} | Select-Object ProcessId, CommandLine | ConvertTo-Json"',
|
||||||
const str = typeof trace === 'string' ? trace : JSON.stringify(trace);
|
{ maxBuffer: 2 * 1024 * 1024 },
|
||||||
console.log(` length=${str.length}`);
|
(err, stdout) => {
|
||||||
console.log(` sample: ${str.substring(0, 500)}`);
|
if (err || !stdout.trim()) {
|
||||||
// If object, show keys
|
console.log(`Gravity Bridge: [LS] process not found`);
|
||||||
const parsed = typeof trace === 'string' ? JSON.parse(trace) : trace;
|
resolve(false);
|
||||||
if (parsed && typeof parsed === 'object') {
|
|
||||||
console.log(` keys: ${Object.keys(parsed).join(', ')}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Gravity Bridge: [getManagerTrace] error: ${e}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probe 2: getWorkbenchTrace — may contain conversation/step data
|
|
||||||
try {
|
|
||||||
const trace: any = await vscode.commands.executeCommand('antigravity.getWorkbenchTrace');
|
|
||||||
console.log(`Gravity Bridge: [getWorkbenchTrace] type=${typeof trace}`);
|
|
||||||
if (trace) {
|
|
||||||
const str = typeof trace === 'string' ? trace : JSON.stringify(trace);
|
|
||||||
console.log(` length=${str.length}`);
|
|
||||||
console.log(` sample: ${str.substring(0, 500)}`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Gravity Bridge: [getWorkbenchTrace] error: ${e}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probe 3: Find LS process for ConnectRPC port + CSRF token
|
|
||||||
try {
|
|
||||||
const { exec } = require('child_process');
|
|
||||||
exec(
|
|
||||||
'powershell -Command "Get-CimInstance Win32_Process | Where-Object {$_.CommandLine -like \'*language_server*\'} | Select-Object ProcessId, CommandLine | Format-List"',
|
|
||||||
{ maxBuffer: 1024 * 1024 },
|
|
||||||
(err: any, stdout: string) => {
|
|
||||||
if (err) {
|
|
||||||
console.log(`Gravity Bridge: [LS discovery] error: ${err.message}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (stdout.trim()) {
|
try {
|
||||||
console.log(`Gravity Bridge: [LS process found]`);
|
let procs = JSON.parse(stdout.trim());
|
||||||
// Extract port and csrf_token from command line
|
if (!Array.isArray(procs)) { procs = [procs]; }
|
||||||
const portMatch = stdout.match(/--(?:port|httpPort|httpsPort)[= ](\d+)/);
|
// Find a process with csrf_token in command line
|
||||||
const csrfMatch = stdout.match(/--csrf_token[= ]([^\s"]+)/);
|
for (const proc of procs) {
|
||||||
const extPortMatch = stdout.match(/--extension_server_port[= ](\d+)/);
|
const cmd = proc.CommandLine || '';
|
||||||
console.log(` port: ${portMatch?.[1] || 'not found'}`);
|
const csrfM = cmd.match(/--csrf_token[= ]([^\s"]+)/);
|
||||||
console.log(` csrf: ${csrfMatch?.[1]?.substring(0, 12) || 'not found'}...`);
|
if (csrfM) {
|
||||||
console.log(` ext_port: ${extPortMatch?.[1] || 'not found'}`);
|
lsPid = proc.ProcessId;
|
||||||
// Show full command line for analysis
|
lsCsrf = csrfM[1];
|
||||||
const lines = stdout.split('\n');
|
console.log(`Gravity Bridge: [LS] PID=${lsPid}, CSRF=${lsCsrf.substring(0, 12)}...`);
|
||||||
for (const line of lines) {
|
break;
|
||||||
if (line.includes('CommandLine')) {
|
|
||||||
console.log(` cmdline: ${line.substring(0, 500)}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} catch (e) {
|
||||||
console.log(`Gravity Bridge: [LS process] not found`);
|
console.log(`Gravity Bridge: [LS] parse error: ${e}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!lsPid || !lsCsrf) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2: netstat → find LS listening ports
|
||||||
|
cp.exec(
|
||||||
|
`netstat -ano | findstr "LISTENING" | findstr " ${lsPid}"`,
|
||||||
|
{ maxBuffer: 512 * 1024 },
|
||||||
|
async (err2, stdout2) => {
|
||||||
|
if (err2 || !stdout2.trim()) {
|
||||||
|
console.log(`Gravity Bridge: [LS] no listening ports found for PID ${lsPid}`);
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Parse ports
|
||||||
|
const ports: number[] = [];
|
||||||
|
for (const line of stdout2.split('\n')) {
|
||||||
|
const m = line.match(/:(\d+)\s+.*LISTENING/);
|
||||||
|
if (m) { ports.push(parseInt(m[1])); }
|
||||||
|
}
|
||||||
|
const uniquePorts = [...new Set(ports)].sort((a, b) => a - b);
|
||||||
|
console.log(`Gravity Bridge: [LS] ports for PID ${lsPid}: ${uniquePorts.join(', ')}`);
|
||||||
|
|
||||||
|
// Try ConnectRPC probe on each port (HTTP first, then HTTPS)
|
||||||
|
for (const port of uniquePorts) {
|
||||||
|
const ok = await probeLSPort(port);
|
||||||
|
if (ok) {
|
||||||
|
lsPort = port;
|
||||||
|
console.log(`Gravity Bridge: [LS] ✅ ConnectRPC active on port ${port}`);
|
||||||
|
resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`Gravity Bridge: [LS] no ConnectRPC port responded`);
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function probeLSPort(port: number): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const http = require('http');
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
// Try HTTP first (extension_server uses HTTP)
|
||||||
|
const tryProto = (proto: any, useTls: boolean) => {
|
||||||
|
const req = proto.request({
|
||||||
|
hostname: '127.0.0.1',
|
||||||
|
port,
|
||||||
|
path: '/exa.language_server_pb.LanguageServerService/GetTrajectoryDescriptions',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-codeium-csrf-token': lsCsrf,
|
||||||
|
},
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
timeout: 2000,
|
||||||
|
}, (res: any) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk: any) => { data += chunk; });
|
||||||
|
res.on('end', () => {
|
||||||
|
// Any response (even error) means ConnectRPC is here
|
||||||
|
console.log(`Gravity Bridge: [LS] port ${port} (${useTls ? 'https' : 'http'}) status=${res.statusCode} body=${data.substring(0, 200)}`);
|
||||||
|
resolve(res.statusCode !== 404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', () => {
|
||||||
|
if (!useTls) {
|
||||||
|
// Try HTTPS
|
||||||
|
tryProto(https, true);
|
||||||
|
} else {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
req.on('timeout', () => { req.destroy(); resolve(false); });
|
||||||
|
req.write('{}');
|
||||||
|
req.end();
|
||||||
|
};
|
||||||
|
tryProto(http, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function lsRPC(method: string, payload: any = {}): Promise<any> {
|
||||||
|
if (!lsPort || !lsCsrf) { return null; }
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const http = require('http');
|
||||||
|
const https = require('https');
|
||||||
|
const proto = lsPort! > 40000 ? https : http; // heuristic
|
||||||
|
const body = JSON.stringify(payload);
|
||||||
|
const req = proto.request({
|
||||||
|
hostname: '127.0.0.1',
|
||||||
|
port: lsPort,
|
||||||
|
path: `/exa.language_server_pb.LanguageServerService/${method}`,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-codeium-csrf-token': lsCsrf,
|
||||||
|
'Content-Length': Buffer.byteLength(body),
|
||||||
|
},
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
timeout: 5000,
|
||||||
|
}, (res: any) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk: any) => { data += chunk; });
|
||||||
|
res.on('end', () => {
|
||||||
|
try { resolve(JSON.parse(data)); } catch { resolve(data); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', (e: any) => {
|
||||||
|
console.log(`Gravity Bridge: [LS RPC] ${method} error: ${e.message}`);
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
req.on('timeout', () => { req.destroy(); resolve(null); });
|
||||||
|
req.write(body);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollConversations() {
|
||||||
|
if (!lsPort) { return; }
|
||||||
|
try {
|
||||||
|
// Get trajectory descriptions — lightweight list of all conversations
|
||||||
|
const result = await lsRPC('GetTrajectoryDescriptions');
|
||||||
|
if (!result) { return; }
|
||||||
|
|
||||||
|
// Debug: log structure on first call
|
||||||
|
if (Object.keys(lastStepIndex).length === 0) {
|
||||||
|
console.log(`Gravity Bridge: [LS] trajectories: ${JSON.stringify(result).substring(0, 500)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const trajectories = result.trajectories || result.trajectory_descriptions || [];
|
||||||
|
if (!Array.isArray(trajectories)) { return; }
|
||||||
|
|
||||||
|
for (const traj of trajectories) {
|
||||||
|
const id = traj.googleAgentId || traj.google_agent_id || traj.id || '';
|
||||||
|
const stepIdx = traj.lastStepIndex ?? traj.last_step_index ?? traj.step_count ?? 0;
|
||||||
|
const prev = lastStepIndex[id];
|
||||||
|
|
||||||
|
if (prev !== undefined && stepIdx > prev) {
|
||||||
|
// New steps! Fetch full trajectory to get AI response
|
||||||
|
console.log(`Gravity Bridge: [LS] ${id.substring(0, 8)} new steps: ${prev} → ${stepIdx}`);
|
||||||
|
const full = await lsRPC('GetCascadeTrajectory', {
|
||||||
|
googleAgentId: id,
|
||||||
|
trajectoryId: traj.trajectoryId || traj.trajectory_id || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (full) {
|
||||||
|
extractAndRelay(full, prev, stepIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastStepIndex[id] = stepIdx;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`Gravity Bridge: [LS discovery] error: ${e}`);
|
console.log(`Gravity Bridge: [LS poll] error: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractAndRelay(trajectory: any, fromStep: number, toStep: number) {
|
||||||
|
// Extract PlannerResponse or assistant messages from trajectory steps
|
||||||
|
const steps = trajectory.steps || trajectory.cortex_steps || [];
|
||||||
|
const messages: string[] = [];
|
||||||
|
|
||||||
|
for (const step of steps) {
|
||||||
|
const idx = step.index ?? step.step_index ?? 0;
|
||||||
|
if (idx <= fromStep) { continue; }
|
||||||
|
|
||||||
|
const type = step.type || step.step_type || '';
|
||||||
|
const content = step.content || step.summary || step.text || '';
|
||||||
|
|
||||||
|
// PlannerResponse = AI's text output to user
|
||||||
|
if ((type === 'PlannerResponse' || type === 'planner_response') && content) {
|
||||||
|
messages.push(content);
|
||||||
|
}
|
||||||
|
// Also capture user-facing messages
|
||||||
|
if (step.data?.content && typeof step.data.content === 'string') {
|
||||||
|
messages.push(step.data.content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Gravity Bridge: === PROBE END ===');
|
// Fallback: if no detailed steps, try messages array
|
||||||
}, 5000);
|
if (messages.length === 0) {
|
||||||
|
const msgs = trajectory.messages || trajectory.chat_messages || [];
|
||||||
|
for (const msg of msgs) {
|
||||||
|
if (msg.role === 'assistant' && msg.content) {
|
||||||
|
messages.push(msg.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messages.length > 0) {
|
||||||
|
const combined = messages.join('\n\n---\n\n');
|
||||||
|
writeChatSnapshot(combined);
|
||||||
|
console.log(`Gravity Bridge: [LS] relayed ${messages.length} response(s) to Discord`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start LS bridge after a delay
|
||||||
|
setTimeout(async () => {
|
||||||
|
const found = await discoverLS();
|
||||||
|
if (found) {
|
||||||
|
console.log(`Gravity Bridge: [LS] bridge active — polling every 5s`);
|
||||||
|
// Initialize step counts
|
||||||
|
await pollConversations();
|
||||||
|
// Start polling loop
|
||||||
|
setInterval(pollConversations, 5000);
|
||||||
|
} else {
|
||||||
|
console.log(`Gravity Bridge: [LS] bridge NOT available — AI responses won't relay`);
|
||||||
|
}
|
||||||
|
}, 8000);
|
||||||
|
|
||||||
|
|
||||||
// Chat document change listener — captures AI text responses
|
// Chat document change listener — captures AI text responses
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
|
|||||||
Reference in New Issue
Block a user