"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}`); } } // ─── 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(); const lastSnapshotText = new Map(); const registeredSessions = new Set(); // track which sessions have been registered /** * Write a registration file for the Bot to discover session → project mapping. * Called automatically on first step event per session. */ function writeRegistration(sessionId) { if (registeredSessions.has(sessionId)) { return; } registeredSessions.add(sessionId); try { const regDir = path.join(bridgePath, 'register'); if (!fs.existsSync(regDir)) { fs.mkdirSync(regDir, { recursive: true }); } const data = { conversation_id: sessionId, project_name: projectName, timestamp: Date.now() / 1000, }; fs.writeFileSync(path.join(regDir, `${sessionId}.json`), JSON.stringify(data, null, 2), 'utf-8'); console.log(`Gravity Bridge: registered session ${sessionId.substring(0, 8)} → ${projectName}`); } catch (e) { console.log(`Gravity Bridge: registration write error: ${e.message}`); } } 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})`); // Auto-register session with Bot on first step event writeRegistration(e.sessionId); // ── ONE-TIME FULL STEP TYPE DUMP ── if (!lastSeenStep.has(e.sessionId)) { try { const fullData = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', { cascadeId: e.sessionId }); if (fullData && Array.isArray(fullData.steps)) { const typeCounts = new Map(); for (const step of fullData.steps) { const t = (step.type || '').replace('CORTEX_STEP_TYPE_', ''); const s = (step.status || '').replace('CORTEX_STEP_STATUS_', ''); const dataKeys = Object.keys(step).filter(k => !['type', 'status', 'metadata'].includes(k)); if (!typeCounts.has(t)) { typeCounts.set(t, { count: 0, statuses: new Set(), keys: new Set(), sample: step }); } const entry = typeCounts.get(t); entry.count++; entry.statuses.add(s); for (const k of dataKeys) { entry.keys.add(k); } } console.log(`Gravity Bridge: ══════════════════════════════════════`); console.log(`Gravity Bridge: FULL STEP TYPE MAP (${fullData.steps.length} total steps)`); console.log(`Gravity Bridge: ══════════════════════════════════════`); for (const [type, info] of typeCounts.entries()) { console.log(`Gravity Bridge: [TYPE] ${type} ×${info.count} statuses=[${[...info.statuses].join(',')}] keys=[${[...info.keys].join(',')}]`); // Dump ONE sample of each type const s = info.sample; const dataKeys = Object.keys(s).filter(k => !['type', 'status', 'metadata'].includes(k)); for (const k of dataKeys) { const v = s[k]; const vStr = typeof v === 'object' ? JSON.stringify(v).substring(0, 150) : String(v).substring(0, 150); console.log(`Gravity Bridge: .${k} (${typeof v}): ${vStr}`); } } console.log(`Gravity Bridge: ══════════════════════════════════════`); } } catch (e) { console.log(`Gravity Bridge: full dump error: ${e.message}`); } } try { // IMPORTANT: Only fetch NEW steps, never re-fetch history const fromStep = Math.max(lastSeenStep.get(e.sessionId) ?? e.newCount - e.delta, e.newCount - e.delta); const stepsData = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', { cascadeId: e.sessionId, startStepIndex: fromStep }); lastSeenStep.set(e.sessionId, e.newCount); if (stepsData && Array.isArray(stepsData.steps)) { // API may ignore startStepIndex — only process the last e.delta steps const allSteps = stepsData.steps; const newSteps = allSteps.slice(-e.delta); console.log(`Gravity Bridge: [SDK] processing ${newSteps.length} of ${allSteps.length} steps`); let lastPlannerText = ''; for (const step of newSteps) { const sType = step.type || ''; const sStatus = step.status || ''; const shortType = sType.replace('CORTEX_STEP_TYPE_', ''); const shortStatus = sStatus.replace('CORTEX_STEP_STATUS_', ''); // ── DIAGNOSTIC: log ALL step types (minimal) ── console.log(`Gravity Bridge: [STEP] ${shortType} ${shortStatus}`); // ══════════════════════════════════════════════ // ANY WAITING step → Pending Approval to Discord // ══════════════════════════════════════════════ if (sStatus.includes('WAITING')) { let description = ''; let command = ''; if (sType.includes('RUN_COMMAND')) { const rc = step.runCommand || {}; command = rc.commandLine || rc.proposedCommandLine || ''; description = `💻 **명령 실행 요청**\n\`\`\`\n${command}\n\`\`\`\ncwd: ${rc.cwd || ''}`; } else if (sType.includes('EDIT_FILE') || sType.includes('CODE_EDIT') || sType.includes('CODE_ACTION') || sType.includes('WRITE_FILE') || sType.includes('FILE_EDIT')) { // File edit/write/code action const edit = step.codeEdit || step.editFile || step.writeFile || step.codeAction || {}; const filePath = edit.filePath || edit.targetFile || edit.path || ''; const desc = edit.description || edit.instruction || ''; command = `📝 파일 수정: ${filePath}`; description = `📝 **파일 변경 확인**\n파일: \`${filePath}\`\n${desc ? `설명: ${desc}` : ''}`; // Full dump for diagnostic console.log(`Gravity Bridge: [STEP-DETAIL] ${shortType} WAITING keys=${JSON.stringify(Object.keys(step).filter(k => !['type', 'status', 'metadata'].includes(k)))}`); for (const k of Object.keys(step).filter(k => !['type', 'status', 'metadata'].includes(k))) { const v = step[k]; console.log(`Gravity Bridge: [STEP-DETAIL] .${k} = ${typeof v === 'object' ? JSON.stringify(v).substring(0, 200) : String(v).substring(0, 200)}`); } } else if (sType.includes('FILE_ACCESS') || sType.includes('READ_FILE') || sType.includes('FILE_READ')) { // File access permission const fa = step.fileAccess || step.readFile || step.fileRead || {}; const filePath = fa.filePath || fa.path || ''; command = `📖 파일 접근: ${filePath}`; description = `📖 **파일 접근 권한 요청**\n파일: \`${filePath}\``; console.log(`Gravity Bridge: [STEP-DETAIL] ${shortType} WAITING keys=${JSON.stringify(Object.keys(step).filter(k => !['type', 'status', 'metadata'].includes(k)))}`); for (const k of Object.keys(step).filter(k => !['type', 'status', 'metadata'].includes(k))) { const v = step[k]; console.log(`Gravity Bridge: [STEP-DETAIL] .${k} = ${typeof v === 'object' ? JSON.stringify(v).substring(0, 200) : String(v).substring(0, 200)}`); } } else { // Unknown WAITING step — still relay it with full diagnostic const stepKeys = Object.keys(step).filter(k => !['type', 'status', 'metadata'].includes(k)); command = `⏳ ${shortType}`; description = `⏳ **대기 중: ${shortType}**\nkeys: ${stepKeys.join(', ')}`; console.log(`Gravity Bridge: [STEP-DETAIL] UNKNOWN WAITING: ${shortType} keys=${JSON.stringify(stepKeys)}`); for (const k of stepKeys) { const v = step[k]; console.log(`Gravity Bridge: [STEP-DETAIL] .${k} = ${typeof v === 'object' ? JSON.stringify(v).substring(0, 200) : String(v).substring(0, 200)}`); } } writePendingApproval({ conversation_id: e.sessionId, command: command, description: `${description}\n\n🏷️ ${e.title}`, }); console.log(`Gravity Bridge: [SDK] ⏳ pending ${shortType}: "${command.substring(0, 80)}"`); continue; } // ══════════════════════════════════════════════ // PLANNER_RESPONSE → AI text relay (COMPLETED/DONE) // ══════════════════════════════════════════════ if (sType.includes('PLANNER_RESPONSE')) { if (!sStatus.includes('COMPLETED') && !sStatus.includes('DONE')) { continue; } const pr = step.plannerResponse; const responseText = pr?.modifiedResponse || pr?.response || ''; if (responseText && typeof responseText === 'string' && responseText.length > 0) { lastPlannerText = responseText; console.log(`Gravity Bridge: [SDK] 📝 planner response found (${responseText.length} chars)`); } continue; } // ══════════════════════════════════════════════ // NOTIFY_USER → also relay as chat snapshot // ══════════════════════════════════════════════ if (sType.includes('NOTIFY_USER') && (sStatus.includes('COMPLETED') || sStatus.includes('DONE'))) { const nu = step.notifyUser; const content = nu?.notificationContent || ''; if (content && content.length > 0) { // Write NOTIFY_USER as snapshot too writeChatSnapshot(`📣 **알림**\n\n${content}`); console.log(`Gravity Bridge: [SDK] 📣 NOTIFY_USER relayed (${content.length} chars)`); } continue; } } // Write the LAST planner response as snapshot (with dedup) if (lastPlannerText && lastPlannerText !== lastSnapshotText.get(e.sessionId)) { lastSnapshotText.set(e.sessionId, lastPlannerText); writeChatSnapshot(`🤖 **${e.title}**\n\n${lastPlannerText}`); console.log(`Gravity Bridge: [SDK] 💬 snapshot written (${lastPlannerText.length} chars)`); } return; } } catch (err) { console.log(`Gravity Bridge: [SDK] steps error: ${err.message}`); } // Fallback lastSeenStep.set(e.sessionId, e.newCount); }); // New conversation started sdk.monitor.onNewConversation((e) => { console.log(`Gravity Bridge: [SDK] new conversation: ${e.title}`); writeRegistration(e.sessionId || e.id || ''); writeChatSnapshot(`🚀 **${e.title}** — 새 대화 시작`); }); // Active session changed sdk.monitor.onActiveSessionChanged((e) => { console.log(`Gravity Bridge: [SDK] active session: "${e.title}" (${e.sessionId?.substring(0, 8)})`); writeRegistration(e.sessionId || e.id || ''); }); // 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)'); // ══════════════════════════════════════════════════════════════════════ // PRIMARY RELAY: Direct step polling via rawRPC (getDiagnostics is stale!) // getDiagnostics.lastStepIndex does NOT update in real-time. // Instead, we poll GetCascadeTrajectorySteps directly for the active session. // ══════════════════════════════════════════════════════════════════════ let pollCount = 0; let activeSessionId = ''; let activeSessionTitle = ''; let polledStepCount = 0; setInterval(async () => { pollCount++; try { // Phase 1: Discover active session (first time or periodically) if (!activeSessionId || pollCount % 12 === 0) { try { const raw = await vscode.commands.executeCommand('antigravity.getDiagnostics'); if (raw && typeof raw === 'string') { const diag = JSON.parse(raw); if (Array.isArray(diag.recentTrajectories) && diag.recentTrajectories.length > 0) { const first = diag.recentTrajectories[0]; if (first.googleAgentId && first.googleAgentId !== activeSessionId) { activeSessionId = first.googleAgentId; activeSessionTitle = first.summary || 'Untitled'; polledStepCount = first.lastStepIndex || 0; console.log(`Gravity Bridge: [POLL#${pollCount}] 🎯 active session: ${activeSessionId.substring(0, 8)} "${activeSessionTitle}" steps=${polledStepCount}`); writeRegistration(activeSessionId); } } } } catch (e) { if (pollCount <= 3) { console.log(`Gravity Bridge: [POLL#${pollCount}] getDiag error: ${e.message}`); } } if (!activeSessionId) return; } // Phase 2: Fetch latest steps via rawRPC (RELIABLE, not stale!) const stepsData = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', { cascadeId: activeSessionId, startStepIndex: polledStepCount > 2 ? polledStepCount - 2 : 0 }); if (!stepsData || !Array.isArray(stepsData.steps)) { if (pollCount <= 3) { console.log(`Gravity Bridge: [POLL#${pollCount}] no steps data`); } return; } const allSteps = stepsData.steps; const currentMax = allSteps.length > 0 ? Math.max(...allSteps.map((s) => s.stepIndex ?? s.index ?? 0), allSteps.length) : polledStepCount; if (currentMax <= polledStepCount) { // No new steps — log every 12th poll if (pollCount % 12 === 0) { console.log(`Gravity Bridge: [POLL#${pollCount}] no change: ${activeSessionId.substring(0, 8)} steps=${currentMax}`); } return; } // Phase 3: New steps detected! Process them. const delta = currentMax - polledStepCount; console.log(`Gravity Bridge: [POLL#${pollCount}] 🆕 +${delta} steps (${polledStepCount}→${currentMax}) "${activeSessionTitle}"`); const newSteps = allSteps.slice(-delta); polledStepCount = currentMax; let lastPlannerText = ''; for (const step of newSteps) { const sType = String(step.type || ''); const sStatus = String(step.status || ''); // PLANNER_RESPONSE → AI text (main content) if (sType.includes('PLANNER_RESPONSE') && (sStatus.includes('DONE') || sStatus.includes('COMPLETED'))) { const pr = step.plannerResponse; const text = pr?.modifiedResponse || pr?.response || ''; if (text && text.length > 0) { lastPlannerText = text; console.log(`Gravity Bridge: [POLL#${pollCount}] 📝 planner ${text.length} chars`); } } // NOTIFY_USER → user notification if (sType.includes('NOTIFY_USER') && (sStatus.includes('DONE') || sStatus.includes('COMPLETED'))) { const nu = step.notifyUser; const content = nu?.notificationContent || ''; if (content && content.length > 0) { writeChatSnapshot(`📣 **알림**\n\n${content}`); console.log(`Gravity Bridge: [POLL#${pollCount}] 📣 notify ${content.length} chars`); } } // TASK_BOUNDARY → task status update if (sType.includes('TASK_BOUNDARY') && (sStatus.includes('DONE') || sStatus.includes('COMPLETED'))) { const tb = step.taskBoundary; if (tb?.taskName) { writeChatSnapshot(`📋 **작업**: ${tb.taskName}\n상태: ${tb.taskStatus || ''}\n${tb.taskSummary || ''}`); } } // WAITING steps → pending approval (any type) if (sStatus.includes('WAITING')) { const shortType = sType.replace(/CORTEX_STEP_TYPE_/g, ''); let command = `⏳ ${shortType}`; let description = `⏳ **대기 중**: ${shortType}`; if (sType.includes('RUN_COMMAND')) { const cmd = step.runCommand?.commandLine || ''; command = `▶️ ${cmd.substring(0, 80)}`; description = `▶️ **명령 실행 확인**\n\`\`\`\n${cmd}\n\`\`\``; } else if (sType.includes('CODE_ACTION') || sType.includes('WRITE')) { const file = step.codeAction?.filePath || step.writeToFile?.filePath || ''; command = `✏️ 파일 수정: ${file}`; description = `✏️ **파일 수정 확인**\n파일: \`${file}\``; } writePendingApproval({ conversation_id: activeSessionId, command: command, description: description, }); console.log(`Gravity Bridge: [POLL#${pollCount}] ⏳ ${shortType} WAITING`); } } // Write latest planner response as snapshot (dedup) if (lastPlannerText && lastPlannerText !== lastSnapshotText.get(activeSessionId)) { lastSnapshotText.set(activeSessionId, lastPlannerText); writeChatSnapshot(`🤖 **${activeSessionTitle}**\n\n${lastPlannerText}`); console.log(`Gravity Bridge: [POLL#${pollCount}] 💬 snapshot ${lastPlannerText.length} chars`); } } catch (e) { if (pollCount <= 5 || pollCount % 12 === 0) { console.log(`Gravity Bridge: [POLL#${pollCount}] error: ${e.message}`); } } }, 5000); } // ─── Response Watcher (Discord approval → Antigravity RPC) ─── let responseWatcher = null; function setupResponseWatcher() { const responseDir = path.join(bridgePath, 'response'); if (!fs.existsSync(responseDir)) { fs.mkdirSync(responseDir, { recursive: true }); } try { responseWatcher = fs.watch(responseDir, (event, filename) => { if (filename && filename.endsWith('.json') && event === 'rename') { const fp = path.join(responseDir, filename); if (fs.existsSync(fp)) { setTimeout(() => processResponseFile(fp), 300); } } }); console.log('Gravity Bridge: response watcher started'); } catch (e) { console.log(`Gravity Bridge: response watcher failed: ${e.message}`); } } async function processResponseFile(filePath) { try { const content = fs.readFileSync(filePath, 'utf-8'); const resp = JSON.parse(content); console.log(`Gravity Bridge: [RESPONSE] request_id=${resp.request_id} approved=${resp.approved}`); if (!sdk) { console.log('Gravity Bridge: [RESPONSE] SDK not available'); return; } // Find matching pending request for session_id const pendingDir = path.join(bridgePath, 'pending'); const pendingFile = path.join(pendingDir, `${resp.request_id}.json`); let sessionId = ''; if (fs.existsSync(pendingFile)) { try { const pending = JSON.parse(fs.readFileSync(pendingFile, 'utf-8')); sessionId = pending.conversation_id || ''; } catch { } } if (sessionId && resp.approved) { try { await sdk.ls.rawRPC('HandleCascadeUserInteraction', { cascadeId: sessionId, approved: true, }); console.log('Gravity Bridge: [RESPONSE] ✅ approved via HandleCascadeUserInteraction'); } catch (e) { console.log(`Gravity Bridge: [RESPONSE] HandleCascadeUserInteraction failed: ${e.message}`); try { await sdk.ls.rawRPC('ResolveOutstandingSteps', { cascadeId: sessionId }); console.log('Gravity Bridge: [RESPONSE] ✅ approved via ResolveOutstandingSteps'); } catch (e2) { console.log(`Gravity Bridge: [RESPONSE] ResolveOutstandingSteps also failed: ${e2.message}`); } } } else { console.log(`Gravity Bridge: [RESPONSE] ${resp.approved ? '✅' : '❌'} (session=${sessionId || 'unknown'})`); } try { fs.unlinkSync(filePath); } catch { } } catch (e) { console.log(`Gravity Bridge: [RESPONSE] error: ${e.message}`); } } /** * Extract AI text from a PLANNER_RESPONSE step. * Known structure: {type, status, metadata, plannerResponse, ephemeralMessage, ...} * ephemeralMessage = system prompt (SKIP), plannerResponse = AI content */ function extractPlannerText(step) { if (!step) { return null; } // Fields to SKIP — not user-facing content const SKIP_FIELDS = new Set([ 'thinking', 'thinkingSignature', 'stopReason', 'type', 'status', 'metadata', 'ephemeralMessage', 'generatorModel', 'requestedModel', 'executionId', 'sourceTrajectoryStepInfo', 'stepIndex', 'viewableAt', 'createdAt', 'finishedGeneratingAt', 'lastCompletedChunkAt', 'source', 'stepGenerationVersion' ]); // plannerResponse can be string or object const pr = step.plannerResponse; if (typeof pr === 'string' && pr.length > 10) { return filterEphemeral(pr); } if (pr && typeof pr === 'object') { // Try known content fields first (NOT thinking/stopReason) const text = pr.content || pr.text || pr.summary || pr.message || pr.response || pr.output; if (typeof text === 'string' && text.length > 10) { return filterEphemeral(text); } // Search other fields, but skip non-content ones for (const key of Object.keys(pr)) { if (SKIP_FIELDS.has(key)) continue; const val = pr[key]; if (typeof val === 'string' && val.length > 50) { // Higher threshold const filtered = filterEphemeral(val); if (filtered) { console.log(`Gravity Bridge: [DEBUG] planner text in plannerResponse.${key} (${filtered.length} chars)`); return filtered; } } } } // Try other step fields (skip known non-content) for (const key of Object.keys(step)) { if (SKIP_FIELDS.has(key) || key === 'plannerResponse') continue; const val = step[key]; if (typeof val === 'string' && val.length > 50) { const filtered = filterEphemeral(val); if (filtered) { console.log(`Gravity Bridge: [DEBUG] planner text in step.${key}`); return filtered; } } } return null; } /** Filter out system ephemeral messages and non-content strings. */ function filterEphemeral(text) { if (!text || text.length < 10) { return null; } // Skip system prompt metadata if (text.includes('') || text.includes('')) { return null; } if (text.includes('artifact_reminder') || text.includes('active_task_reminder')) { return null; } if (text.includes('no_active_task_reminder')) { return null; } // Skip base64/crypto strings (no spaces, mostly alphanumeric) if (!text.includes(' ') && /^[A-Za-z0-9+/=_\-]{50,}$/.test(text)) { return null; } return text; } /** Write a pending approval file matching Bot's ApprovalRequest dataclass. */ function writePendingApproval(data) { try { const pendingDir = path.join(bridgePath, 'pending'); if (!fs.existsSync(pendingDir)) { fs.mkdirSync(pendingDir, { recursive: true }); } const id = Date.now().toString(); const payload = { request_id: id, conversation_id: data.conversation_id, command: data.command, description: data.description, timestamp: Date.now() / 1000, status: 'pending', discord_message_id: 0, project_name: projectName, }; fs.writeFileSync(path.join(pendingDir, `${id}.json`), JSON.stringify(payload, null, 2), 'utf-8'); console.log(`Gravity Bridge: pending approval written → ${id}.json`); } catch (e) { console.log(`Gravity Bridge: pending write error: ${e.message}`); } } // ─── 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(); // Watch response directory for approval interactions setupResponseWatcher(); // 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