342 lines
13 KiB
JavaScript
342 lines
13 KiB
JavaScript
"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
|