- Snapshot: response/chat_snapshot.txt → chat_snapshots/*.json - Command field: cmd.message → cmd.text (matches Bot.write_command) - RPC: GetConversation (404) → GetCascadeTrajectorySteps - Bundle sql-wasm.js + sql-wasm.wasm into VSIX (45KB→379KB) - Handle consumed flag, clean 38 stale commands - Add extractAIText helper with fallback chain
455 lines
17 KiB
JavaScript
455 lines
17 KiB
JavaScript
"use strict";
|
|
/**
|
|
* Gravity Bridge — VS Code Extension (SDK Edition)
|
|
*
|
|
* Uses antigravity-sdk for:
|
|
* - Real-time step/conversation monitoring via EventMonitor
|
|
* - Full conversation content via LSBridge.getConversation()
|
|
* - Message sending via CascadeManager.sendPrompt()
|
|
* - Accept/Reject via CascadeManager.acceptStep()/rejectStep()
|
|
*
|
|
* Communication with Discord via file-based bridge protocol.
|
|
*/
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || (function () {
|
|
var ownKeys = function(o) {
|
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
var ar = [];
|
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
return ar;
|
|
};
|
|
return ownKeys(o);
|
|
};
|
|
return function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.activate = activate;
|
|
exports.deactivate = deactivate;
|
|
const vscode = __importStar(require("vscode"));
|
|
const fs = __importStar(require("fs"));
|
|
const path = __importStar(require("path"));
|
|
const os = __importStar(require("os"));
|
|
const cp = __importStar(require("child_process"));
|
|
// antigravity-sdk embedded locally (src/sdk/)
|
|
let AntigravitySDK;
|
|
let sdk;
|
|
let statusBar;
|
|
let bridgePath;
|
|
let projectName;
|
|
let isActive = false;
|
|
let watcher = null;
|
|
let commandsWatcher = null;
|
|
const sentPendingIds = new Set();
|
|
// ─── Project Detection ───
|
|
function detectProjectName() {
|
|
const config = vscode.workspace.getConfiguration('gravityBridge');
|
|
const configName = config.get('projectName');
|
|
if (configName) {
|
|
return configName;
|
|
}
|
|
const folders = vscode.workspace.workspaceFolders;
|
|
if (folders && folders.length > 0) {
|
|
const cwd = folders[0].uri.fsPath;
|
|
try {
|
|
const remoteUrl = cp.execSync('git remote get-url origin', {
|
|
cwd, encoding: 'utf-8', timeout: 3000
|
|
}).trim();
|
|
const match = remoteUrl.match(/\/([^\/]+?)(?:\.git)?$/);
|
|
if (match && match[1]) {
|
|
return match[1].toLowerCase().replace(/[\s\-]+/g, '_');
|
|
}
|
|
}
|
|
catch { }
|
|
return path.basename(cwd).toLowerCase().replace(/[\s\-]+/g, '_');
|
|
}
|
|
return 'default';
|
|
}
|
|
// ─── Bridge File I/O ───
|
|
function ensureBridgeDir() {
|
|
const dirs = ['', 'response', 'commands', 'chat_snapshots'];
|
|
for (const d of dirs) {
|
|
const p = path.join(bridgePath, d);
|
|
if (!fs.existsSync(p)) {
|
|
fs.mkdirSync(p, { recursive: true });
|
|
}
|
|
}
|
|
}
|
|
function writeChatSnapshot(text) {
|
|
try {
|
|
// Write to chat_snapshots/*.json for Bot's chat_snapshot_scanner to pick up
|
|
const snapshotDir = path.join(bridgePath, 'chat_snapshots');
|
|
if (!fs.existsSync(snapshotDir)) {
|
|
fs.mkdirSync(snapshotDir, { recursive: true });
|
|
}
|
|
const id = Date.now().toString();
|
|
const data = {
|
|
id,
|
|
project_name: projectName,
|
|
content: text,
|
|
timestamp: Date.now() / 1000,
|
|
};
|
|
const filePath = path.join(snapshotDir, `${id}.json`);
|
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
console.log(`Gravity Bridge: chat snapshot written (${text.length} chars) → ${id}.json`);
|
|
}
|
|
catch (e) {
|
|
console.log(`Gravity Bridge: snapshot write error: ${e.message}`);
|
|
}
|
|
}
|
|
function writePendingApproval(data) {
|
|
try {
|
|
const filePath = path.join(bridgePath, 'response', 'pending_approval.json');
|
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
}
|
|
catch { }
|
|
}
|
|
// ─── Command File Watcher (Discord → Antigravity) ───
|
|
function processCommandFile(filePath) {
|
|
try {
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
const cmd = JSON.parse(content);
|
|
// Skip already consumed commands
|
|
if (cmd.consumed) {
|
|
try {
|
|
fs.unlinkSync(filePath);
|
|
}
|
|
catch { }
|
|
return;
|
|
}
|
|
// Ignore commands for other projects
|
|
if (cmd.project_name && cmd.project_name !== projectName) {
|
|
console.log(`Gravity Bridge: skipping command for "${cmd.project_name}" (we are "${projectName}")`);
|
|
return;
|
|
}
|
|
// Bot writes 'text' field, not 'message'
|
|
const text = cmd.text || cmd.message || '';
|
|
const action = cmd.action || '';
|
|
console.log(`Gravity Bridge: command — text="${text}" action="${action}"`);
|
|
if (action === 'approve' && sdk) {
|
|
sdk.cascade.acceptStep().catch((e) => console.log(`Gravity Bridge: approve error: ${e.message}`));
|
|
}
|
|
else if (action === 'reject' && sdk) {
|
|
sdk.cascade.rejectStep().catch((e) => console.log(`Gravity Bridge: reject error: ${e.message}`));
|
|
}
|
|
else if (action === 'approve_terminal' && sdk) {
|
|
sdk.cascade.acceptTerminalCommand().catch((e) => console.log(`Gravity Bridge: approve_terminal error: ${e.message}`));
|
|
}
|
|
else if (text === '!stop') {
|
|
// Cancel current operation
|
|
vscode.commands.executeCommand('antigravity.agent.rejectAgentStep')
|
|
.then(() => console.log('Gravity Bridge: ✅ stop sent'), () => { });
|
|
}
|
|
else if (text.startsWith('!auto ')) {
|
|
// Auto-approve mode toggle
|
|
const mode = text.includes('on') ? 'true' : 'false';
|
|
console.log(`Gravity Bridge: auto-approve → ${mode}`);
|
|
}
|
|
else if (text) {
|
|
// Send message to Antigravity — use VS Code command (most reliable)
|
|
vscode.commands.executeCommand('antigravity.sendPromptToAgentPanel', text)
|
|
.then(() => console.log(`Gravity Bridge: ✅ sent "${text.substring(0, 50)}" via sendPromptToAgentPanel`), (e) => console.log(`Gravity Bridge: sendPrompt failed: ${e.message}`));
|
|
}
|
|
// Remove processed command file
|
|
try {
|
|
fs.unlinkSync(filePath);
|
|
}
|
|
catch { }
|
|
}
|
|
catch (e) {
|
|
console.log(`Gravity Bridge: command processing error: ${e.message}`);
|
|
}
|
|
}
|
|
function watchCommandsDir() {
|
|
const cmdDir = path.join(bridgePath, 'commands');
|
|
// Process existing files
|
|
try {
|
|
for (const f of fs.readdirSync(cmdDir)) {
|
|
if (f.endsWith('.json')) {
|
|
processCommandFile(path.join(cmdDir, f));
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
// Watch for new files
|
|
try {
|
|
commandsWatcher = fs.watch(cmdDir, (event, filename) => {
|
|
if (filename && filename.endsWith('.json') && event === 'rename') {
|
|
const fp = path.join(cmdDir, filename);
|
|
if (fs.existsSync(fp)) {
|
|
setTimeout(() => processCommandFile(fp), 200);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
catch { }
|
|
}
|
|
// ─── SDK Integration ───
|
|
async function initSDK(context) {
|
|
try {
|
|
const sdkModule = require('./sdk/index');
|
|
AntigravitySDK = sdkModule.AntigravitySDK;
|
|
}
|
|
catch (err) {
|
|
console.log(`Gravity Bridge: antigravity-sdk load failed: ${err.message}`);
|
|
return false;
|
|
}
|
|
try {
|
|
sdk = new AntigravitySDK(context);
|
|
await sdk.initialize();
|
|
console.log('Gravity Bridge: ✅ SDK initialized');
|
|
return true;
|
|
}
|
|
catch (err) {
|
|
console.log(`Gravity Bridge: SDK init failed: ${err.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
// Track last seen step per session to avoid re-fetching
|
|
const lastSeenStep = new Map();
|
|
function setupMonitor() {
|
|
if (!sdk) {
|
|
return;
|
|
}
|
|
// Step count changed → fetch new steps via GetCascadeTrajectorySteps
|
|
sdk.monitor.onStepCountChanged(async (e) => {
|
|
console.log(`Gravity Bridge: [SDK] step changed: "${e.title}" step ${e.newCount} (+${e.delta})`);
|
|
try {
|
|
// Use the correct LS RPC: GetCascadeTrajectorySteps (not GetConversation which doesn't exist)
|
|
const fromStep = lastSeenStep.get(e.sessionId) ?? Math.max(0, e.newCount - e.delta);
|
|
const stepsData = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
|
|
cascadeId: e.sessionId,
|
|
startStepIndex: fromStep
|
|
});
|
|
lastSeenStep.set(e.sessionId, e.newCount);
|
|
if (stepsData) {
|
|
// Try to extract AI text from the steps response
|
|
const aiText = extractAIText(stepsData);
|
|
if (aiText) {
|
|
const text = `🤖 **${e.title}**\n\n${aiText}`;
|
|
writeChatSnapshot(text);
|
|
console.log(`Gravity Bridge: [SDK] relayed AI response (${aiText.length} chars)`);
|
|
return;
|
|
}
|
|
// Log the raw structure for debugging
|
|
console.log(`Gravity Bridge: [SDK] steps data keys: ${JSON.stringify(Object.keys(stepsData))}`);
|
|
}
|
|
}
|
|
catch (err) {
|
|
console.log(`Gravity Bridge: [SDK] GetCascadeTrajectorySteps error: ${err.message}`);
|
|
// Fallback: try GetCascadeTrajectory (full trajectory, heavier)
|
|
try {
|
|
const fullTraj = await sdk.ls.rawRPC('GetCascadeTrajectory', {
|
|
cascadeId: e.sessionId
|
|
});
|
|
if (fullTraj) {
|
|
const aiText = extractAIText(fullTraj);
|
|
if (aiText) {
|
|
writeChatSnapshot(`🤖 **${e.title}**\n\n${aiText}`);
|
|
console.log(`Gravity Bridge: [SDK] relayed via GetCascadeTrajectory (${aiText.length} chars)`);
|
|
return;
|
|
}
|
|
console.log(`Gravity Bridge: [SDK] trajectory keys: ${JSON.stringify(Object.keys(fullTraj))}`);
|
|
}
|
|
}
|
|
catch (err2) {
|
|
console.log(`Gravity Bridge: [SDK] GetCascadeTrajectory also failed: ${err2.message}`);
|
|
}
|
|
}
|
|
// Fallback: just send the title + step info
|
|
lastSeenStep.set(e.sessionId, e.newCount);
|
|
writeChatSnapshot(`🤖 **${e.title}**\n\n(step ${e.newCount}, +${e.delta})`);
|
|
});
|
|
// New conversation started
|
|
sdk.monitor.onNewConversation(() => {
|
|
console.log('Gravity Bridge: [SDK] new conversation detected');
|
|
});
|
|
// Active session changed
|
|
sdk.monitor.onActiveSessionChanged((e) => {
|
|
console.log(`Gravity Bridge: [SDK] active session: "${e.title}" (${e.sessionId?.substring(0, 8)})`);
|
|
});
|
|
// State changed (USS update)
|
|
sdk.monitor.onStateChanged((e) => {
|
|
console.log(`Gravity Bridge: [SDK] state changed: ${e.key}`);
|
|
});
|
|
// Start monitoring (USS every 3s, trajectory every 2s for faster detection)
|
|
sdk.monitor.start(3000, 2000);
|
|
console.log('Gravity Bridge: [SDK] monitor started (USS 3s, trajectory 2s)');
|
|
}
|
|
/**
|
|
* Extract AI response text from LS RPC step/trajectory data.
|
|
* The exact structure depends on the protobuf schema — we try multiple paths.
|
|
*/
|
|
function extractAIText(data) {
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
// Try common protobuf response patterns
|
|
// Pattern 1: steps array with content
|
|
const steps = data.steps || data.trajectorySteps || data.cascadeSteps;
|
|
if (Array.isArray(steps) && steps.length > 0) {
|
|
// Find the last step with AI content
|
|
for (let i = steps.length - 1; i >= 0; i--) {
|
|
const step = steps[i];
|
|
// PlannerResponse / assistant content
|
|
const content = step.content || step.text || step.summary ||
|
|
step.plannerResponse || step.assistantMessage ||
|
|
step.response?.content || step.response?.text;
|
|
if (typeof content === 'string' && content.length > 10) {
|
|
return content;
|
|
}
|
|
}
|
|
}
|
|
// Pattern 2: messages array
|
|
const messages = data.messages || data.chatMessages;
|
|
if (Array.isArray(messages) && messages.length > 0) {
|
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
const msg = messages[i];
|
|
if ((msg.role === 'assistant' || msg.type === 'assistant') && msg.content) {
|
|
return msg.content;
|
|
}
|
|
}
|
|
}
|
|
// Pattern 3: nested trajectory object
|
|
if (data.trajectory) {
|
|
return extractAIText(data.trajectory);
|
|
}
|
|
// Pattern 4: single step response
|
|
if (data.content && typeof data.content === 'string') {
|
|
return data.content;
|
|
}
|
|
return null;
|
|
}
|
|
// ─── Activation ───
|
|
async function activate(context) {
|
|
console.log('Gravity Bridge: activating...');
|
|
// Project detection
|
|
projectName = detectProjectName();
|
|
console.log(`Gravity Bridge: project "${projectName}"`);
|
|
// Bridge path
|
|
const config = vscode.workspace.getConfiguration('gravityBridge');
|
|
const configPath = config.get('bridgePath');
|
|
bridgePath = configPath || path.join(os.homedir(), '.gemini', 'antigravity', 'bridge');
|
|
ensureBridgeDir();
|
|
console.log(`Gravity Bridge: bridge path: ${bridgePath}`);
|
|
// Status bar
|
|
statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
|
|
statusBar.text = '$(sync~spin) Bridge';
|
|
statusBar.tooltip = `Gravity Bridge: ${projectName}`;
|
|
statusBar.show();
|
|
context.subscriptions.push(statusBar);
|
|
// Initialize SDK
|
|
const sdkReady = await initSDK(context);
|
|
if (sdkReady) {
|
|
setupMonitor();
|
|
statusBar.text = '$(check) Bridge SDK';
|
|
statusBar.tooltip = `Gravity Bridge: ${projectName} (SDK active)`;
|
|
// Register SDK-powered commands
|
|
context.subscriptions.push(vscode.commands.registerCommand('gravityBridge.approve', async () => {
|
|
try {
|
|
await sdk.cascade.acceptStep();
|
|
vscode.window.showInformationMessage('Gravity Bridge: Step approved');
|
|
}
|
|
catch (e) {
|
|
vscode.window.showErrorMessage(`Approve failed: ${e.message}`);
|
|
}
|
|
}), vscode.commands.registerCommand('gravityBridge.reject', async () => {
|
|
try {
|
|
await sdk.cascade.rejectStep();
|
|
vscode.window.showInformationMessage('Gravity Bridge: Step rejected');
|
|
}
|
|
catch (e) {
|
|
vscode.window.showErrorMessage(`Reject failed: ${e.message}`);
|
|
}
|
|
}));
|
|
}
|
|
else {
|
|
statusBar.text = '$(warning) Bridge (no SDK)';
|
|
console.log('Gravity Bridge: SDK not available, file-based mode only');
|
|
}
|
|
// Watch commands directory
|
|
watchCommandsDir();
|
|
// Register basic commands
|
|
context.subscriptions.push(vscode.commands.registerCommand('gravityBridge.start', () => {
|
|
isActive = true;
|
|
statusBar.text = sdkReady ? '$(check) Bridge SDK' : '$(sync~spin) Bridge';
|
|
vscode.window.showInformationMessage(`Gravity Bridge started for "${projectName}"`);
|
|
}), vscode.commands.registerCommand('gravityBridge.stop', () => {
|
|
isActive = false;
|
|
if (sdk) {
|
|
sdk.monitor.stop();
|
|
}
|
|
statusBar.text = '$(circle-slash) Bridge OFF';
|
|
vscode.window.showInformationMessage('Gravity Bridge stopped');
|
|
}), vscode.commands.registerCommand('gravityBridge.connect', async () => {
|
|
if (!sdk) {
|
|
vscode.window.showErrorMessage('SDK not initialized');
|
|
return;
|
|
}
|
|
try {
|
|
const sessions = await sdk.cascade.getSessions();
|
|
const items = sessions.map((s) => ({
|
|
label: s.title || 'Untitled',
|
|
description: `step ${s.stepCount} • ${s.id?.substring(0, 8)}`,
|
|
sessionId: s.id,
|
|
}));
|
|
const pick = await vscode.window.showQuickPick(items, {
|
|
placeHolder: 'Select a conversation to connect'
|
|
});
|
|
if (pick) {
|
|
await sdk.cascade.focusSession(pick.sessionId);
|
|
vscode.window.showInformationMessage(`Connected to: ${pick.label}`);
|
|
}
|
|
}
|
|
catch (e) {
|
|
vscode.window.showErrorMessage(`Connect failed: ${e.message}`);
|
|
}
|
|
}));
|
|
// Cleanup
|
|
context.subscriptions.push({
|
|
dispose: () => {
|
|
if (sdk) {
|
|
sdk.monitor.stop();
|
|
sdk.dispose();
|
|
}
|
|
if (watcher) {
|
|
watcher.close();
|
|
}
|
|
if (commandsWatcher) {
|
|
commandsWatcher.close();
|
|
}
|
|
}
|
|
});
|
|
console.log('Gravity Bridge: ✅ activated');
|
|
isActive = true;
|
|
}
|
|
function deactivate() {
|
|
if (sdk) {
|
|
try {
|
|
sdk.monitor.stop();
|
|
sdk.dispose();
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
//# sourceMappingURL=extension.js.map
|