"use strict"; /** * Gravity Bridge — VS Code Extension * * Bridges Antigravity IDE approval dialogs to the Discord bot via file-based protocol. * * Flow: * 1. Extension watches for tool approval notifications in VS Code * 2. Writes pending approval to bridge/pending/ * 3. Discord bot sends buttons to user * 4. User clicks approve/reject * 5. Bot writes response to bridge/response/ * 6. Extension reads response → sends keyboard command to approve/reject */ 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.writePendingApproval = writePendingApproval; exports.deactivate = deactivate; const vscode = __importStar(require("vscode")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); let watcher = null; let statusBar; let bridgePath; let isActive = false; // Track pending approvals we've already sent const sentPendingIds = new Set(); function activate(context) { console.log('Gravity Bridge: activating...'); // Determine bridge path const config = vscode.workspace.getConfiguration('gravityBridge'); const configPath = config.get('bridgePath'); bridgePath = configPath || path.join(os.homedir(), '.gemini', 'antigravity', 'bridge'); // Ensure bridge directories exist const dirs = ['pending', 'response', 'commands']; for (const dir of dirs) { const dirPath = path.join(bridgePath, dir); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } // Status bar statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); statusBar.command = 'gravityBridge.start'; statusBar.text = '$(radio-tower) Bridge: Off'; statusBar.tooltip = 'Gravity Bridge — Click to start'; statusBar.show(); context.subscriptions.push(statusBar); // Register commands context.subscriptions.push(vscode.commands.registerCommand('gravityBridge.start', startBridge), vscode.commands.registerCommand('gravityBridge.stop', stopBridge), vscode.commands.registerCommand('gravityBridge.approve', () => handleManualAction(true)), vscode.commands.registerCommand('gravityBridge.reject', () => handleManualAction(false))); // Auto-start startBridge(); } function startBridge() { if (isActive) { vscode.window.showInformationMessage('Gravity Bridge is already running'); return; } isActive = true; statusBar.text = '$(radio-tower) Bridge: On'; statusBar.tooltip = 'Gravity Bridge — Active'; statusBar.command = 'gravityBridge.stop'; // Watch bridge/response/ for Discord user responses const responsePath = path.join(bridgePath, 'response'); const processedFiles = new Set(); // Debounce: prevent double-processing try { watcher = fs.watch(responsePath, { persistent: false }, (eventType, filename) => { if (filename && filename.endsWith('.json') && !processedFiles.has(filename)) { processedFiles.add(filename); setTimeout(() => processedFiles.delete(filename), 2000); handleResponse(path.join(responsePath, filename)); } }); console.log('Gravity Bridge: watching response directory'); } catch (err) { console.error('Gravity Bridge: failed to watch response dir', err); } // Watch for commands (user text input from Discord) const commandsPath = path.join(bridgePath, 'commands'); try { fs.watch(commandsPath, { persistent: false }, (eventType, filename) => { if (filename && filename.endsWith('.json') && !processedFiles.has(filename)) { processedFiles.add(filename); setTimeout(() => processedFiles.delete(filename), 2000); handleCommand(path.join(commandsPath, filename)); } }); console.log('Gravity Bridge: watching commands directory'); } catch (err) { console.error('Gravity Bridge: failed to watch commands dir', err); } vscode.window.showInformationMessage('Gravity Bridge: Started'); console.log(`Gravity Bridge: started, bridge path: ${bridgePath}`); } function stopBridge() { if (!isActive) { return; } isActive = false; statusBar.text = '$(radio-tower) Bridge: Off'; statusBar.tooltip = 'Gravity Bridge — Click to start'; statusBar.command = 'gravityBridge.start'; if (watcher) { watcher.close(); watcher = null; } vscode.window.showInformationMessage('Gravity Bridge: Stopped'); } /** * Handle a response from Discord (approve/reject). * Reads the response JSON and simulates the appropriate action. */ async function handleResponse(filePath) { try { // Small delay to ensure file is fully written await new Promise(resolve => setTimeout(resolve, 200)); if (!fs.existsSync(filePath)) { return; } const content = fs.readFileSync(filePath, 'utf-8'); const response = JSON.parse(content); if (response.approved === undefined) { return; } console.log(`Gravity Bridge: response received — approved=${response.approved}`); if (response.approved) { // Simulate pressing Enter or clicking approve // Strategy: Use VS Code command to accept suggestion await simulateApproval(); vscode.window.showInformationMessage(`✅ Approved: ${response.request_id}`); } else { await simulateRejection(); vscode.window.showInformationMessage(`❌ Rejected: ${response.request_id}`); } // Cleanup: delete the response file after processing try { fs.unlinkSync(filePath); } catch (e) { /* ignore */ } } catch (err) { console.error('Gravity Bridge: error handling response', err); } } /** * Handle a text command from Discord. * Supports special commands (!auto on/off) and general text relay. */ async function handleCommand(filePath) { try { await new Promise(resolve => setTimeout(resolve, 200)); if (!fs.existsSync(filePath)) { return; } const content = fs.readFileSync(filePath, 'utf-8'); const command = JSON.parse(content); if (command.consumed || !command.text) { return; } const text = command.text.trim(); console.log(`Gravity Bridge: command received — "${text.substring(0, 50)}"`); // Special command: auto-approve toggle if (text === '!auto on' || text === '!auto off') { const enabled = text === '!auto on'; await toggleAutoApprove(enabled); // Mark as consumed command.consumed = true; fs.writeFileSync(filePath, JSON.stringify(command, null, 2), 'utf-8'); return; } // General text: type into chat panel await vscode.commands.executeCommand('workbench.action.chat.open'); await new Promise(resolve => setTimeout(resolve, 500)); const oldClipboard = await vscode.env.clipboard.readText(); await vscode.env.clipboard.writeText(command.text); await vscode.commands.executeCommand('editor.action.clipboardPasteAction'); await vscode.env.clipboard.writeText(oldClipboard); // Mark as consumed command.consumed = true; fs.writeFileSync(filePath, JSON.stringify(command, null, 2), 'utf-8'); vscode.window.showInformationMessage(`📨 Discord input: ${command.text.substring(0, 50)}...`); } catch (err) { console.error('Gravity Bridge: error handling command', err); } } /** * Toggle Antigravity's auto-approve settings. */ async function toggleAutoApprove(enabled) { const config = vscode.workspace.getConfiguration(); try { // Core auto-approve settings await config.update('chat.tools.autoApprove', enabled, vscode.ConfigurationTarget.Global); await config.update('chat.agent.autoApprove', enabled, vscode.ConfigurationTarget.Global); // Terminal auto-execution if (enabled) { await config.update('chat.tools.terminal.enableAutoApprove', true, vscode.ConfigurationTarget.Global); } // File edits auto-accept await config.update('autoAcceptV2.autoAcceptFileEdits', enabled, vscode.ConfigurationTarget.Global); // Update status bar statusBar.text = enabled ? '$(radio-tower) Bridge: Auto ✅' : '$(radio-tower) Bridge: Manual 🔒'; const mode = enabled ? '자동 승인 ON 🟢' : '수동 승인 OFF 🔴'; vscode.window.showInformationMessage(`Gravity Bridge: ${mode}`); console.log(`Gravity Bridge: auto-approve set to ${enabled}`); // Write status back to bridge for bot to report const statusPath = path.join(bridgePath, 'commands', `auto-status-${Date.now()}.json`); fs.writeFileSync(statusPath, JSON.stringify({ id: `auto-status-${Date.now()}`, text: `[SYSTEM] Auto-approve: ${enabled ? 'ON' : 'OFF'}`, timestamp: Date.now() / 1000, consumed: true, auto_approve: enabled, }, null, 2), 'utf-8'); } catch (err) { console.error('Gravity Bridge: failed to toggle auto-approve', err); vscode.window.showErrorMessage(`Auto-approve toggle failed: ${err}`); } } /** * Simulate approval — try multiple strategies. */ async function simulateApproval() { try { // Strategy 1: Try executing the accept command if available await vscode.commands.executeCommand('workbench.action.acceptSelectedCodeAction'); } catch { // Strategy 2: Send Enter key via type command try { await vscode.commands.executeCommand('type', { text: '\n' }); } catch { // Strategy 3: Focus terminal and send Enter await vscode.commands.executeCommand('workbench.action.terminal.focus'); } } } /** * Simulate rejection — try multiple strategies. */ async function simulateRejection() { try { // Strategy 1: Escape key await vscode.commands.executeCommand('workbench.action.closeQuickOpen'); } catch { try { await vscode.commands.executeCommand('cancelSelection'); } catch { // Fallback: just notify console.log('Gravity Bridge: rejection sent but no active dialog found'); } } } /** * Manual approve/reject from command palette. * Writes a pending request for testing purposes. */ function handleManualAction(approved) { const requestId = `manual-${Date.now()}`; const responsePath = path.join(bridgePath, 'response', `${requestId}.json`); const response = { request_id: requestId, approved: approved, user_input: '', timestamp: Date.now() / 1000, }; fs.writeFileSync(responsePath, JSON.stringify(response, null, 2), 'utf-8'); if (approved) { simulateApproval(); } else { simulateRejection(); } } /** * Write a pending approval request to bridge/pending/ for Discord bot to pick up. */ function writePendingApproval(conversationId, command, description) { const requestId = `req-${Date.now()}`; const pendingPath = path.join(bridgePath, 'pending', `${requestId}.json`); const request = { request_id: requestId, conversation_id: conversationId, command: command, description: description, timestamp: Date.now() / 1000, status: 'pending', discord_message_id: 0, }; fs.writeFileSync(pendingPath, JSON.stringify(request, null, 2), 'utf-8'); sentPendingIds.add(requestId); console.log(`Gravity Bridge: pending approval written — ${requestId}`); return requestId; } function deactivate() { stopBridge(); } //# sourceMappingURL=extension.js.map