642 lines
28 KiB
JavaScript
642 lines
28 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"));
|
|
const ws_client_1 = require("./ws-client");
|
|
const step_probe_1 = require("./step-probe");
|
|
const http_bridge_1 = require("./http-bridge");
|
|
const html_patcher_1 = require("./html-patcher");
|
|
const command_handler_1 = require("./command-handler");
|
|
// ─── File-based logging (AI can read directly) ───
|
|
function logToFile(msg) {
|
|
const ts = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
// Include projectName prefix so shared log can distinguish which extension instance logged
|
|
const prefix = projectName ? `[${projectName}]` : '';
|
|
const line = `${ts} ${prefix} ${msg}`;
|
|
console.log(`Gravity Bridge: ${prefix} ${msg}`);
|
|
try {
|
|
if (!bridgePath)
|
|
return;
|
|
const logFile = path.join(bridgePath, 'extension.log');
|
|
fs.appendFileSync(logFile, line + '\n', 'utf-8');
|
|
}
|
|
catch (e) {
|
|
console.error(`Gravity Bridge LOG WRITE FAIL: ${e.message}`);
|
|
}
|
|
}
|
|
// antigravity-sdk embedded locally (src/sdk/)
|
|
let AntigravitySDK;
|
|
let sdk;
|
|
let statusBar;
|
|
let bridgePath;
|
|
let projectName;
|
|
let workspaceUri = ''; // filesystem path of the workspace folder (for session filtering)
|
|
let isActive = false;
|
|
let autoApproveEnabled = false; // toggled via !auto from Discord
|
|
let watcher = null;
|
|
let wsBridge = null; // WebSocket Hub connection
|
|
const sentPendingIds = new Set();
|
|
// Memory-based dedup: tracks recently created pending step_indexes to prevent
|
|
// regeneration after pending file deletion (by Collector/Bot response cycle).
|
|
// Map<string, number> = `${conversationId}:${stepIndex}` → creation timestamp
|
|
const recentPendingSteps = new Map();
|
|
const PENDING_MEMORY_TTL_MS = 60_000; // 60 seconds memory retention
|
|
// In-memory cache for diff_review metadata (survives pending file deletion by Collector).
|
|
// Map<request_id, { edit_step_indices, modified_files }>
|
|
const diffReviewMetadata = new Map();
|
|
// ─── 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 });
|
|
}
|
|
}
|
|
}
|
|
// Module-level activeSessionId so writeChatSnapshot can register sessions lazily
|
|
let activeSessionId = '';
|
|
let activeTrajectoryId = '';
|
|
// Track recently sent Discord→AG texts to avoid echo relay
|
|
const recentDiscordSentTexts = new Map();
|
|
function writeChatSnapshot(text) {
|
|
try {
|
|
// WS route (preferred) — skip file write to prevent duplicate Discord delivery
|
|
if (wsBridge && wsBridge.isConnected()) {
|
|
wsBridge.sendChat({
|
|
content: text,
|
|
conversation_id: activeSessionId,
|
|
project_name: projectName,
|
|
});
|
|
logToFile(`[SNAPSHOT-WS] sent (${text.length} chars)`);
|
|
if (activeSessionId) {
|
|
(0, step_probe_1.writeRegistration)(activeSessionId);
|
|
}
|
|
return;
|
|
}
|
|
// File route (fallback — only when WS is NOT connected)
|
|
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`);
|
|
logToFile(`[SNAPSHOT] written ${id}.json (${text.length} chars)`);
|
|
logToFile(`[SNAPSHOT] content: ${text.substring(0, 200)}`);
|
|
// Lazily register session → project mapping (correct because projectName is per-window)
|
|
if (activeSessionId) {
|
|
(0, step_probe_1.writeRegistration)(activeSessionId);
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.log(`Gravity Bridge: snapshot write error: ${e.message}`);
|
|
}
|
|
}
|
|
function writeChatSnapshotWithFiles(text, files) {
|
|
try {
|
|
// WS route (preferred) — skip file write to prevent duplicate Discord delivery
|
|
if (wsBridge && wsBridge.isConnected()) {
|
|
wsBridge.sendChat({
|
|
content: text,
|
|
attached_files: files,
|
|
conversation_id: activeSessionId,
|
|
project_name: projectName,
|
|
});
|
|
logToFile(`[SNAPSHOT-WS] sent with ${files.length} files (${text.length} chars)`);
|
|
if (activeSessionId) {
|
|
(0, step_probe_1.writeRegistration)(activeSessionId);
|
|
}
|
|
return;
|
|
}
|
|
// File route (fallback — only when WS is NOT connected)
|
|
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,
|
|
attached_files: files,
|
|
timestamp: Date.now() / 1000,
|
|
};
|
|
const filePath = path.join(snapshotDir, `${id}.json`);
|
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
logToFile(`[SNAPSHOT] written ${id}.json (${text.length} chars, ${files.length} files)`);
|
|
if (activeSessionId) {
|
|
(0, step_probe_1.writeRegistration)(activeSessionId);
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.log(`Gravity Bridge: snapshot+files write error: ${e.message}`);
|
|
}
|
|
}
|
|
// ─── Command handling extracted to ./command-handler.ts ───
|
|
// ─── 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');
|
|
// ── 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) {
|
|
console.log(`Gravity Bridge: SDK init failed: ${err.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* 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() {
|
|
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;
|
|
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) => 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 = 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;
|
|
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 = [];
|
|
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((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) => 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) {
|
|
logToFile(`[LS-FIX] error: ${err.message}`);
|
|
}
|
|
}
|
|
// ─── Approval Observer + Product.json Checksums extracted to ./html-patcher.ts ───
|
|
// ─── HTTP Bridge Server extracted to ./http-bridge.ts ───
|
|
// Shared state for HTTP bridge context (module-level, referenced by BridgeContext too)
|
|
let sessionStalled = false;
|
|
let lastPendingStepIndex = -1;
|
|
let stallProbed = false;
|
|
let sawRunningAfterPending = true;
|
|
// ─── Step Probe, Response Watcher, Approval Strategies → extracted to ./step-probe.ts ───
|
|
async function activate(context) {
|
|
console.log('Gravity Bridge: activating...');
|
|
// Project detection
|
|
projectName = detectProjectName();
|
|
// Store workspace folder path for session filtering (prevents cross-window session grabbing)
|
|
const folders = vscode.workspace.workspaceFolders;
|
|
workspaceUri = folders && folders.length > 0 ? folders[0].uri.fsPath : '';
|
|
console.log(`Gravity Bridge: project "${projectName}" workspace="${workspaceUri}"`);
|
|
// 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}`);
|
|
// ── WebSocket Hub Connection ──
|
|
const hubUrl = process.env.GRAVITY_HUB_URL || config.get('hubUrl') || '';
|
|
const regCode = process.env.GRAVITY_REGISTRATION_CODE || config.get('registrationCode') || '';
|
|
const pcName = os.hostname();
|
|
if (hubUrl) {
|
|
wsBridge = new ws_client_1.WSBridgeClient(hubUrl, regCode, projectName, pcName, {
|
|
onResponse: (data) => {
|
|
logToFile(`[WS-RESPONSE] ${data.request_id?.substring(0, 12)} approved=${data.approved} step_type=${data.step_type || '(none)'}`);
|
|
const approved = data.approved ?? true;
|
|
const stepType = data.step_type || '';
|
|
// ── diff_review: Accept all / Reject all (REGRESSION FIX) ──
|
|
// Previously only handled in processResponseFile (file-bridge path).
|
|
// WS path was missing this logic entirely, causing Accept All to fail.
|
|
if (stepType === 'diff_review') {
|
|
logToFile(`[WS-RESPONSE] diff_review detected — routing to handleDiffReviewResponse`);
|
|
(0, step_probe_1.handleDiffReviewResponse)({
|
|
request_id: data.request_id,
|
|
approved,
|
|
button_index: data.button_index,
|
|
step_type: stepType,
|
|
})
|
|
.then(result => {
|
|
logToFile(`[WS-RESPONSE] diff_review result: ${result}`);
|
|
(0, step_probe_1.resetPendingState)();
|
|
})
|
|
.catch(err => logToFile(`[WS-RESPONSE] diff_review error: ${err.message}`));
|
|
return;
|
|
}
|
|
// Normal approval — tryApprovalStrategies
|
|
const approvalCtx = (0, step_probe_1.getApprovalContext)();
|
|
logToFile(`[WS-RESPONSE] Triggering approval: approved=${approved} session=${approvalCtx.sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${approvalCtx.stepIndex}`);
|
|
(0, step_probe_1.tryApprovalStrategies)(approved, approvalCtx.sessionId, stepType, approvalCtx.stepIndex)
|
|
.then(result => {
|
|
logToFile(`[WS-RESPONSE] Approval result: ${result}`);
|
|
(0, step_probe_1.resetPendingState)();
|
|
})
|
|
.catch(err => logToFile(`[WS-RESPONSE] Approval error: ${err.message}`));
|
|
},
|
|
onCommand: (data) => {
|
|
logToFile(`[WS-CMD] ${data.text?.substring(0, 50)}`);
|
|
(0, command_handler_1.handleWSCommand)({
|
|
bridgePath, projectName, sdk, ls: sdk?.ls, autoApproveEnabled, logToFile,
|
|
onAutoApproveChanged: (enabled) => { autoApproveEnabled = enabled; },
|
|
recentDiscordSentTexts,
|
|
}, data);
|
|
},
|
|
onInstanceUpdate: (count, instances) => {
|
|
logToFile(`[WS-INSTANCE] ${count} active instances`);
|
|
},
|
|
onConnected: (connId, instanceNum, token) => {
|
|
logToFile(`[WS] Connected: ${connId} instance=#${instanceNum}`);
|
|
statusBar.text = '$(check) Bridge WS';
|
|
statusBar.tooltip = `Gravity Bridge: ${projectName} (WS #${instanceNum})`;
|
|
},
|
|
onDisconnected: () => {
|
|
logToFile('[WS] Disconnected — using file fallback');
|
|
statusBar.text = '$(warning) Bridge (WS ↓)';
|
|
},
|
|
onError: (err) => {
|
|
logToFile(`[WS-ERR] ${err}`);
|
|
},
|
|
}, logToFile);
|
|
wsBridge.connect();
|
|
logToFile(`[WS] Hub connection initiated: ${hubUrl}`);
|
|
}
|
|
else {
|
|
logToFile('[WS] No GRAVITY_HUB_URL — WebSocket disabled, using file bridge only');
|
|
}
|
|
// ── Multi-project: no lock file, each project uses project_name-based filtering ──
|
|
// (active_project.lock removed — was blocking concurrent multi-project usage)
|
|
logToFile(`[INIT] project="${projectName}" pid=${process.pid} — multi-project mode (no lock)`);
|
|
// 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) {
|
|
// ── Command Discovery Diagnostic ──
|
|
// Enumerate ALL antigravity.* commands to find correct approval command names
|
|
try {
|
|
const allCmds = await vscode.commands.getCommands(true);
|
|
const agCmds = allCmds.filter((c) => c.startsWith('antigravity.'));
|
|
logToFile(`[CMD-DISCOVERY] Total antigravity.* commands: ${agCmds.length}`);
|
|
// Log approval-related commands specifically
|
|
const approvalKeywords = ['accept', 'reject', 'approve', 'terminal', 'agent', 'cascade', 'step', 'run', 'command.'];
|
|
const relevantCmds = agCmds.filter((c) => approvalKeywords.some(kw => c.toLowerCase().includes(kw)));
|
|
logToFile(`[CMD-DISCOVERY] Approval-related commands (${relevantCmds.length}):`);
|
|
for (const cmd of relevantCmds) {
|
|
logToFile(`[CMD-DISCOVERY] → ${cmd}`);
|
|
}
|
|
// Also dump ALL commands for full reference
|
|
logToFile(`[CMD-DISCOVERY] ALL antigravity.* commands:`);
|
|
for (const cmd of agCmds) {
|
|
logToFile(`[CMD-DISCOVERY] ${cmd}`);
|
|
}
|
|
}
|
|
catch (e) {
|
|
logToFile(`[CMD-DISCOVERY] error: ${e.message}`);
|
|
}
|
|
// Initialize step probe (polling + response watcher)
|
|
(0, step_probe_1.initStepProbe)({
|
|
bridgePath,
|
|
projectName,
|
|
sdk,
|
|
wsBridge,
|
|
autoApproveEnabled,
|
|
activeSessionId,
|
|
sessionStalled,
|
|
lastPendingStepIndex,
|
|
stallProbed,
|
|
sawRunningAfterPending,
|
|
setClickTrigger: (action) => {
|
|
const { setClickTrigger: setTrigger } = require('./http-bridge');
|
|
setTrigger(action);
|
|
},
|
|
logToFile,
|
|
workspaceUri,
|
|
diffReviewMetadata: new Map(),
|
|
recentDiscordSentTexts,
|
|
writeChatSnapshot,
|
|
writeChatSnapshotWithFiles,
|
|
});
|
|
// Start HTTP bridge, then setup observer
|
|
const httpBridgeCtx = {
|
|
bridgePath, projectName, activeSessionId, wsBridge,
|
|
sessionStalled, lastPendingStepIndex, logToFile,
|
|
};
|
|
const bridgePort = await (0, http_bridge_1.startHttpBridge)(httpBridgeCtx, sdk);
|
|
if (bridgePort) {
|
|
await (0, html_patcher_1.setupApprovalObserver)(sdk, bridgePort, logToFile);
|
|
}
|
|
else {
|
|
logToFile('[OBSERVER] HTTP bridge failed — skipping observer setup');
|
|
}
|
|
statusBar.text = '$(check) Bridge';
|
|
statusBar.tooltip = `Gravity Bridge: ${projectName} (POLL + Observer 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
|
|
(0, command_handler_1.watchCommandsDir)({
|
|
bridgePath, projectName, sdk, ls: sdk?.ls, autoApproveEnabled, logToFile,
|
|
onAutoApproveChanged: (enabled) => { autoApproveEnabled = enabled; },
|
|
recentDiscordSentTexts,
|
|
});
|
|
// Response watcher is now initialized by initStepProbe() above
|
|
// 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;
|
|
// SDK monitor is disabled, no need to 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) {
|
|
try {
|
|
sdk.dispose();
|
|
}
|
|
catch { }
|
|
}
|
|
if (watcher) {
|
|
watcher.close();
|
|
}
|
|
(0, command_handler_1.disposeCommandsWatcher)();
|
|
}
|
|
});
|
|
console.log('Gravity Bridge: ✅ activated');
|
|
isActive = true;
|
|
}
|
|
function deactivate() {
|
|
// Disconnect WebSocket
|
|
if (wsBridge) {
|
|
wsBridge.disconnect();
|
|
wsBridge = null;
|
|
}
|
|
// Clean up stale lock file if it exists (legacy cleanup)
|
|
try {
|
|
const lockFile = path.join(bridgePath, 'active_project.lock');
|
|
if (fs.existsSync(lockFile)) {
|
|
const lockData = JSON.parse(fs.readFileSync(lockFile, 'utf-8'));
|
|
if (lockData.pid === process.pid) {
|
|
fs.unlinkSync(lockFile);
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
if (sdk) {
|
|
try {
|
|
sdk.dispose();
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
//# sourceMappingURL=extension.js.map
|