feat(extension): Gravity Web Bridge — antigravity-sdk based VS Code extension with REST/WS bridge

This commit is contained in:
2026-03-07 23:41:57 +09:00
parent aa0b06c661
commit 54147cf695
5 changed files with 1324 additions and 0 deletions

2
extension/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
out/
node_modules/

1035
extension/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
extension/package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "gravity-web-bridge",
"displayName": "Gravity Web Bridge",
"description": "Antigravity ↔ Gravity Web 브릿지 — 대화 스트리밍, 메시지 전송, 승인/거절 제어",
"version": "0.1.0",
"publisher": "variet",
"engines": {
"vscode": "^1.85.0"
},
"categories": ["Other"],
"activationEvents": ["onStartupFinished"],
"main": "./out/extension.js",
"scripts": {
"build": "tsc -p .",
"watch": "tsc -watch -p .",
"package": "vsce package"
},
"dependencies": {
"antigravity-sdk": "latest",
"express": "^4.18.0",
"ws": "^8.16.0"
},
"devDependencies": {
"@types/vscode": "^1.85.0",
"@types/ws": "^8.5.0",
"@types/express": "^4.17.0",
"typescript": "^5.3.0"
}
}

238
extension/src/extension.ts Normal file
View File

@@ -0,0 +1,238 @@
/**
* 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<any>();
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.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 });
}
});
// --- 서버 시작 ---
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() { }

20
extension/tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "out",
"lib": [
"ES2020"
],
"sourceMap": true,
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true
},
"exclude": [
"node_modules",
"out"
]
}