/** * Gravity Web Bridge Extension * * Antigravity IDE 내부에서 동작하여: * 1. antigravity-sdk로 대화/상태 접근 * 2. 로컬 HTTP/WS 서버를 열어 Gravity Web 서버와 통신 * * API: * GET /api/sessions → 대화 목록 * GET /api/session/:id → 대화 상세 * POST /api/send → 메시지 전송 * POST /api/accept → 스텝 승인 * POST /api/reject → 스텝 거절 * WS /ws → 실시간 이벤트 스트림 */ import * as vscode from 'vscode'; // antigravity-sdk는 런타임에 로드 (VS Code Extension 환경에서만 동작) let AntigravitySDK: any; const BRIDGE_PORT = 9850; export async function activate(context: vscode.ExtensionContext) { const output = vscode.window.createOutputChannel('Gravity Web Bridge'); output.appendLine('[Bridge] Extension 활성화...'); // antigravity-sdk 로드 try { const sdkModule = require('antigravity-sdk'); AntigravitySDK = sdkModule.AntigravitySDK; } catch (err: any) { output.appendLine(`[Bridge] antigravity-sdk 로드 실패: ${err.message}`); vscode.window.showErrorMessage('Gravity Web Bridge: antigravity-sdk를 찾을 수 없습니다.'); return; } // SDK 초기화 const sdk = new AntigravitySDK(context); try { await sdk.initialize(); output.appendLine('[Bridge] SDK 초기화 완료'); } catch (err: any) { output.appendLine(`[Bridge] SDK 초기화 실패: ${err.message}`); return; } // Express + WS 서버 const express = require('express'); const http = require('http'); const { WebSocketServer } = require('ws'); const app = express(); const server = http.createServer(app); const wss = new WebSocketServer({ server, path: '/ws' }); app.use(express.json()); // CORS 허용 app.use((_req: any, res: any, next: any) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Content-Type'); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); next(); }); // --- REST API --- app.get('/api/sessions', async (_req: any, res: any) => { try { const sessions = await sdk.cascade.getSessions(); res.json({ sessions }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); app.post('/api/send', async (req: any, res: any) => { try { const { message, sessionId } = req.body; if (sessionId) { await sdk.cascade.focusSession(sessionId); } await sdk.cascade.sendPrompt(message); res.json({ success: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); app.post('/api/accept', async (_req: any, res: any) => { try { await sdk.cascade.acceptStep(); res.json({ success: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); app.post('/api/reject', async (_req: any, res: any) => { try { await sdk.cascade.rejectStep(); res.json({ success: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); app.post('/api/accept-terminal', async (_req: any, res: any) => { try { await sdk.cascade.acceptTerminalCommand(); res.json({ success: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); app.get('/api/preferences', async (_req: any, res: any) => { try { const prefs = await sdk.cascade.getPreferences(); res.json({ preferences: prefs }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); app.get('/api/health', (_req: any, res: any) => { res.json({ status: 'ok', port: BRIDGE_PORT, version: '0.1.0' }); }); // --- WebSocket 이벤트 스트림 --- const wsClients = new Set(); wss.on('connection', (ws: any) => { wsClients.add(ws); output.appendLine('[Bridge WS] 클라이언트 연결'); ws.on('close', () => { wsClients.delete(ws); output.appendLine('[Bridge WS] 클라이언트 해제'); }); // 초기 세션 목록 전송 sdk.cascade.getSessions().then((sessions: any) => { ws.send(JSON.stringify({ type: 'sessions', sessions })); }).catch(() => { }); }); function broadcast(data: any) { const json = JSON.stringify(data); for (const ws of wsClients) { if (ws.readyState === 1) { // WebSocket.OPEN ws.send(json); } } } // SDK 모니터 이벤트 → WS 브로드캐스트 sdk.monitor.onStepCountChanged((e: any) => { broadcast({ type: 'step_changed', title: e.title, delta: e.delta, newCount: e.newCount, sessionId: e.sessionId, }); }); sdk.monitor.onActiveSessionChanged((e: any) => { broadcast({ type: 'session_changed', title: e.title, sessionId: e.sessionId, }); }); sdk.monitor.onNewConversation(() => { broadcast({ type: 'new_conversation' }); // 세션 목록 갱신 sdk.cascade.getSessions().then((sessions: any) => { broadcast({ type: 'sessions', sessions }); }).catch(() => { }); }); sdk.monitor.onStateChanged((e: any) => { broadcast({ type: 'state_changed', key: e.key, previousSize: e.previousSize, newSize: e.newSize, }); }); sdk.monitor.start(3000, 5000); // USS 3초, trajectory 5초 폴링 // --- LS Bridge (Language Server 직접 접근) --- app.get('/api/cascades', async (_req: any, res: any) => { try { const cascades = await sdk.ls.listCascades(); res.json({ cascades }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // 개별 대화 전체 내용 (스텝, 메시지, 도구 호출 등) app.get('/api/conversation/:id', async (req: any, res: any) => { try { const conversation = await sdk.ls.getConversation(req.params.id); res.json(conversation); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // 임의 LS RPC 호출 (개발/디버그) app.post('/api/ls/rpc', async (req: any, res: any) => { try { const { method, payload } = req.body; const result = await sdk.ls.rawRPC(method, payload || {}); res.json(result); } catch (err: any) { res.status(500).json({ error: err.message }); } }); app.post('/api/ls/send', async (req: any, res: any) => { try { const { cascadeId, text, model } = req.body; await sdk.ls.sendMessage({ cascadeId, text, model }); res.json({ success: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // --- 승인/거절 액션 (Gravity Web에서 직접 조작) --- app.post('/api/action', async (req: any, res: any) => { try { const { action } = req.body; const commandMap: Record = { acceptStep: 'antigravity.agent.acceptAgentStep', rejectStep: 'antigravity.agent.rejectAgentStep', acceptCommand: 'antigravity.command.accept', rejectCommand: 'antigravity.command.reject', acceptTerminal: 'antigravity.terminalCommand.accept', rejectTerminal: 'antigravity.terminalCommand.reject', }; const cmd = commandMap[action]; if (!cmd) { res.status(400).json({ error: `Unknown action: ${action}` }); return; } await vscode.commands.executeCommand(cmd); output.appendLine(`[Action] ${action} → ${cmd} 실행`); res.json({ success: true, action }); } catch (err: any) { output.appendLine(`[Action] 실패: ${err.message}`); res.status(500).json({ error: err.message }); } }); // --- 서버 시작 --- server.listen(BRIDGE_PORT, () => { output.appendLine(`[Bridge] 서버 시작: http://localhost:${BRIDGE_PORT}`); vscode.window.showInformationMessage( `Gravity Web Bridge 활성화 — localhost:${BRIDGE_PORT}` ); }); // 정리 context.subscriptions.push(sdk); context.subscriptions.push({ dispose: () => { server.close(); sdk.monitor.stop(); output.appendLine('[Bridge] 서버 종료'); }, }); } export function deactivate() { }