docs: mid-session devlog + entry 002 (sendPromptToAgentPanel discovery)
This commit is contained in:
@@ -10,3 +10,7 @@
|
|||||||
| 6 | 02:25 | Extension 스캐폴드 + VSIX 빌드 | `52fed8c`~`1af5fb7` | ✅ |
|
| 6 | 02:25 | Extension 스캐폴드 + VSIX 빌드 | `52fed8c`~`1af5fb7` | ✅ |
|
||||||
| 7 | 11:40 | 전면 재설계 (채널 스팸/중복 해결) | `e32be6b`~`51ece61` | ✅ |
|
| 7 | 11:40 | 전면 재설계 (채널 스팸/중복 해결) | `e32be6b`~`51ece61` | ✅ |
|
||||||
| 8 | 12:17 | 승인 중복 전송 수정 + E2E 테스트 통과 | `ce2336c` | ✅ |
|
| 8 | 12:17 | 승인 중복 전송 수정 + E2E 테스트 통과 | `ce2336c` | ✅ |
|
||||||
|
| 9 | 13:00 | 채팅 제출 탐색: clipboard paste + Enter 시뮬레이션 | `7f15e98`~`35f39ab` | 🔧 |
|
||||||
|
| 10 | 15:00 | @bridge→@gravity 이름 변경 + 슬래시 명령어 /stop /auto /send | `02e9e4d`~`0bd525a` | ✅ |
|
||||||
|
| 11 | 16:00 | sendTextToChat 탐색 → sendPromptToAgentPanel 발견 | `e4eb756`~`8d5e59c` | ✅ |
|
||||||
|
| 12 | 17:15 | 양방향 통신 완성 + 171개 명령어 문서화 + scanner 시작 수정 | `befa5d7` | ✅ |
|
||||||
|
|||||||
27
docs/devlog/entries/20260307-002.md
Normal file
27
docs/devlog/entries/20260307-002.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Discord ↔ Antigravity 양방향 채팅 통합
|
||||||
|
|
||||||
|
- **시간**: 2026-03-07 13:00~17:15
|
||||||
|
- **Commit**: `7f15e98`~`befa5d7` (15 commits)
|
||||||
|
- **Vikunja**: #223 → 진행중
|
||||||
|
|
||||||
|
## 결정 사항
|
||||||
|
|
||||||
|
### sendPromptToAgentPanel 발견 경위
|
||||||
|
- clipboard paste → Enter 시뮬레이션 (`\r`, `default:Enter`, `\u000d`) → 전부 실패
|
||||||
|
- `antigravity.sendTextToChat(true/false)` → 에러 없이 실행되지만 텍스트 표시 안 됨
|
||||||
|
- `vscode.commands.getCommands(true)` 로 171개 내부 명령어 전수 조사
|
||||||
|
- **`antigravity.sendPromptToAgentPanel`** 발견 → 성공
|
||||||
|
|
||||||
|
### Chat Snapshot 파이프라인 (응답 릴레이)
|
||||||
|
- Extension `onDidChangeTextDocument` → chat scheme 필터 → 2초 debounce → `bridge/chat_snapshots/`
|
||||||
|
- Bot `chat_snapshot_scanner` @tasks.loop(5초) → Discord embed 전송
|
||||||
|
- **`.start()` 누락 발견** — 두 scanner 모두 정의만 되고 시작 안 됐음
|
||||||
|
|
||||||
|
### SDK (antigravity-sdk)
|
||||||
|
- npm 패키지 존재, README에 `sendPrompt`, `sendMessage` 등 광고
|
||||||
|
- 실제 JS 코드에 해당 함수 미구현 (커뮤니티 프로젝트, vaporware)
|
||||||
|
- `state.vscdb` 읽기 + `vscode.commands.executeCommand` 래핑만 구현됨
|
||||||
|
|
||||||
|
## 미완료
|
||||||
|
- 응답 릴레이(Antigravity→Discord) 테스트 중
|
||||||
|
- listener leak (351개 누적) — Extension 재로드 시 dispose 문제
|
||||||
Binary file not shown.
BIN
extension/gravity-bridge-0.2.0.vsix
Normal file
BIN
extension/gravity-bridge-0.2.0.vsix
Normal file
Binary file not shown.
@@ -4,13 +4,10 @@
|
|||||||
*
|
*
|
||||||
* Bridges Antigravity IDE approval dialogs to the Discord bot via file-based protocol.
|
* Bridges Antigravity IDE approval dialogs to the Discord bot via file-based protocol.
|
||||||
*
|
*
|
||||||
* Flow:
|
* Multi-project routing:
|
||||||
* 1. Extension watches for tool approval notifications in VS Code
|
* - Each workspace has a project name (from settings or workspace folder name)
|
||||||
* 2. Writes pending approval to bridge/pending/
|
* - Extension only processes commands/responses matching its project_name
|
||||||
* 3. Discord bot sends buttons to user
|
* - Pending approvals include project_name for Discord channel routing
|
||||||
* 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) {
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
if (k2 === undefined) k2 = k;
|
if (k2 === undefined) k2 = k;
|
||||||
@@ -54,19 +51,58 @@ const fs = __importStar(require("fs"));
|
|||||||
const path = __importStar(require("path"));
|
const path = __importStar(require("path"));
|
||||||
const os = __importStar(require("os"));
|
const os = __importStar(require("os"));
|
||||||
let watcher = null;
|
let watcher = null;
|
||||||
|
let commandsWatcher = null;
|
||||||
let statusBar;
|
let statusBar;
|
||||||
let bridgePath;
|
let bridgePath;
|
||||||
|
let projectName;
|
||||||
let isActive = false;
|
let isActive = false;
|
||||||
// Track pending approvals we've already sent
|
// Track pending approvals we've already sent
|
||||||
const sentPendingIds = new Set();
|
const sentPendingIds = new Set();
|
||||||
|
const cp = __importStar(require("child_process"));
|
||||||
|
/**
|
||||||
|
* Detect project name from workspace.
|
||||||
|
* Priority: settings > git remote repo name > workspace folder name
|
||||||
|
*/
|
||||||
|
function detectProjectName() {
|
||||||
|
const config = vscode.workspace.getConfiguration('gravityBridge');
|
||||||
|
const configName = config.get('projectName');
|
||||||
|
if (configName) {
|
||||||
|
return configName;
|
||||||
|
}
|
||||||
|
// Try git remote URL → extract repo name
|
||||||
|
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();
|
||||||
|
// "https://gitea.example.com/Variet/gravity_control.git" → "gravity_control"
|
||||||
|
// "git@github.com:user/repo.git" → "repo"
|
||||||
|
const match = remoteUrl.match(/\/([^\/]+?)(?:\.git)?$/);
|
||||||
|
if (match && match[1]) {
|
||||||
|
const repoName = match[1].toLowerCase().replace(/[\s\-]+/g, '_');
|
||||||
|
console.log(`Gravity Bridge: project from git remote → "${repoName}"`);
|
||||||
|
return repoName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// No git or no remote — fall through
|
||||||
|
}
|
||||||
|
// Fallback: workspace folder name
|
||||||
|
return folders[0].name.toLowerCase().replace(/[\s\-]+/g, '_');
|
||||||
|
}
|
||||||
|
return 'unknown_project';
|
||||||
|
}
|
||||||
function activate(context) {
|
function activate(context) {
|
||||||
console.log('Gravity Bridge: activating...');
|
projectName = detectProjectName();
|
||||||
|
console.log(`Gravity Bridge: activating for project "${projectName}"...`);
|
||||||
// Determine bridge path
|
// Determine bridge path
|
||||||
const config = vscode.workspace.getConfiguration('gravityBridge');
|
const config = vscode.workspace.getConfiguration('gravityBridge');
|
||||||
const configPath = config.get('bridgePath');
|
const configPath = config.get('bridgePath');
|
||||||
bridgePath = configPath || path.join(os.homedir(), '.gemini', 'antigravity', 'bridge');
|
bridgePath = configPath || path.join(os.homedir(), '.gemini', 'antigravity', 'bridge');
|
||||||
// Ensure bridge directories exist
|
// Ensure bridge directories exist
|
||||||
const dirs = ['pending', 'response', 'commands'];
|
const dirs = ['pending', 'response', 'commands', 'register'];
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
const dirPath = path.join(bridgePath, dir);
|
const dirPath = path.join(bridgePath, dir);
|
||||||
if (!fs.existsSync(dirPath)) {
|
if (!fs.existsSync(dirPath)) {
|
||||||
@@ -76,27 +112,43 @@ function activate(context) {
|
|||||||
// Status bar
|
// Status bar
|
||||||
statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
|
statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
|
||||||
statusBar.command = 'gravityBridge.start';
|
statusBar.command = 'gravityBridge.start';
|
||||||
statusBar.text = '$(radio-tower) Bridge: Off';
|
statusBar.text = `$(radio-tower) ${projectName}: Off`;
|
||||||
statusBar.tooltip = 'Gravity Bridge — Click to start';
|
statusBar.tooltip = `Gravity Bridge — ${projectName}`;
|
||||||
statusBar.show();
|
statusBar.show();
|
||||||
context.subscriptions.push(statusBar);
|
context.subscriptions.push(statusBar);
|
||||||
// Register commands
|
// 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)));
|
context.subscriptions.push(vscode.commands.registerCommand('gravityBridge.start', startBridge), vscode.commands.registerCommand('gravityBridge.stop', stopBridge), vscode.commands.registerCommand('gravityBridge.connect', connectSession), vscode.commands.registerCommand('gravityBridge.approve', () => handleManualAction(true)), vscode.commands.registerCommand('gravityBridge.reject', () => handleManualAction(false)));
|
||||||
|
// Chat document change listener — captures AI text responses
|
||||||
|
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument((event) => {
|
||||||
|
handleChatDocumentChange(event);
|
||||||
|
}));
|
||||||
|
// Register @bridge Chat Participant for history relay
|
||||||
|
try {
|
||||||
|
const participant = vscode.chat.createChatParticipant('gravity-bridge.gravity', bridgeChatHandler);
|
||||||
|
participant.iconPath = new vscode.ThemeIcon('radio-tower');
|
||||||
|
context.subscriptions.push(participant);
|
||||||
|
console.log('Gravity Bridge: @bridge chat participant registered');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log('Gravity Bridge: chat participant API not available (OK)');
|
||||||
|
}
|
||||||
|
// Auto-watch brain/ for new conversations → auto-register
|
||||||
|
watchBrainForNewSessions();
|
||||||
// Auto-start
|
// Auto-start
|
||||||
startBridge();
|
startBridge();
|
||||||
}
|
}
|
||||||
function startBridge() {
|
function startBridge() {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
vscode.window.showInformationMessage('Gravity Bridge is already running');
|
vscode.window.showInformationMessage(`Gravity Bridge (${projectName}): already running`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isActive = true;
|
isActive = true;
|
||||||
statusBar.text = '$(radio-tower) Bridge: On';
|
statusBar.text = `$(radio-tower) ${projectName}: On`;
|
||||||
statusBar.tooltip = 'Gravity Bridge — Active';
|
statusBar.tooltip = `Gravity Bridge — ${projectName} (Active)`;
|
||||||
statusBar.command = 'gravityBridge.stop';
|
statusBar.command = 'gravityBridge.stop';
|
||||||
// Watch bridge/response/ for Discord user responses
|
// Watch bridge/response/ for Discord user responses
|
||||||
const responsePath = path.join(bridgePath, 'response');
|
const responsePath = path.join(bridgePath, 'response');
|
||||||
const processedFiles = new Set(); // Debounce: prevent double-processing
|
const processedFiles = new Set(); // Debounce
|
||||||
try {
|
try {
|
||||||
watcher = fs.watch(responsePath, { persistent: false }, (eventType, filename) => {
|
watcher = fs.watch(responsePath, { persistent: false }, (eventType, filename) => {
|
||||||
if (filename && filename.endsWith('.json') && !processedFiles.has(filename)) {
|
if (filename && filename.endsWith('.json') && !processedFiles.has(filename)) {
|
||||||
@@ -113,7 +165,7 @@ function startBridge() {
|
|||||||
// Watch for commands (user text input from Discord)
|
// Watch for commands (user text input from Discord)
|
||||||
const commandsPath = path.join(bridgePath, 'commands');
|
const commandsPath = path.join(bridgePath, 'commands');
|
||||||
try {
|
try {
|
||||||
fs.watch(commandsPath, { persistent: false }, (eventType, filename) => {
|
commandsWatcher = fs.watch(commandsPath, { persistent: false }, (eventType, filename) => {
|
||||||
if (filename && filename.endsWith('.json') && !processedFiles.has(filename)) {
|
if (filename && filename.endsWith('.json') && !processedFiles.has(filename)) {
|
||||||
processedFiles.add(filename);
|
processedFiles.add(filename);
|
||||||
setTimeout(() => processedFiles.delete(filename), 2000);
|
setTimeout(() => processedFiles.delete(filename), 2000);
|
||||||
@@ -125,30 +177,33 @@ function startBridge() {
|
|||||||
catch (err) {
|
catch (err) {
|
||||||
console.error('Gravity Bridge: failed to watch commands dir', err);
|
console.error('Gravity Bridge: failed to watch commands dir', err);
|
||||||
}
|
}
|
||||||
vscode.window.showInformationMessage('Gravity Bridge: Started');
|
vscode.window.showInformationMessage(`Gravity Bridge (${projectName}): Started`);
|
||||||
console.log(`Gravity Bridge: started, bridge path: ${bridgePath}`);
|
console.log(`Gravity Bridge: started for project "${projectName}", bridge: ${bridgePath}`);
|
||||||
}
|
}
|
||||||
function stopBridge() {
|
function stopBridge() {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isActive = false;
|
isActive = false;
|
||||||
statusBar.text = '$(radio-tower) Bridge: Off';
|
statusBar.text = `$(radio-tower) ${projectName}: Off`;
|
||||||
statusBar.tooltip = 'Gravity Bridge — Click to start';
|
statusBar.tooltip = `Gravity Bridge — ${projectName}`;
|
||||||
statusBar.command = 'gravityBridge.start';
|
statusBar.command = 'gravityBridge.start';
|
||||||
if (watcher) {
|
if (watcher) {
|
||||||
watcher.close();
|
watcher.close();
|
||||||
watcher = null;
|
watcher = null;
|
||||||
}
|
}
|
||||||
vscode.window.showInformationMessage('Gravity Bridge: Stopped');
|
if (commandsWatcher) {
|
||||||
|
commandsWatcher.close();
|
||||||
|
commandsWatcher = null;
|
||||||
|
}
|
||||||
|
vscode.window.showInformationMessage(`Gravity Bridge (${projectName}): Stopped`);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Handle a response from Discord (approve/reject).
|
* Handle a response from Discord (approve/reject).
|
||||||
* Reads the response JSON and simulates the appropriate action.
|
* Only processes responses — no project filtering needed since request_id is unique.
|
||||||
*/
|
*/
|
||||||
async function handleResponse(filePath) {
|
async function handleResponse(filePath) {
|
||||||
try {
|
try {
|
||||||
// Small delay to ensure file is fully written
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
return;
|
return;
|
||||||
@@ -158,10 +213,8 @@ async function handleResponse(filePath) {
|
|||||||
if (response.approved === undefined) {
|
if (response.approved === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`Gravity Bridge: response received — approved=${response.approved}`);
|
console.log(`Gravity Bridge [${projectName}]: response — approved=${response.approved}`);
|
||||||
if (response.approved) {
|
if (response.approved) {
|
||||||
// Simulate pressing Enter or clicking approve
|
|
||||||
// Strategy: Use VS Code command to accept suggestion
|
|
||||||
await simulateApproval();
|
await simulateApproval();
|
||||||
vscode.window.showInformationMessage(`✅ Approved: ${response.request_id}`);
|
vscode.window.showInformationMessage(`✅ Approved: ${response.request_id}`);
|
||||||
}
|
}
|
||||||
@@ -169,7 +222,6 @@ async function handleResponse(filePath) {
|
|||||||
await simulateRejection();
|
await simulateRejection();
|
||||||
vscode.window.showInformationMessage(`❌ Rejected: ${response.request_id}`);
|
vscode.window.showInformationMessage(`❌ Rejected: ${response.request_id}`);
|
||||||
}
|
}
|
||||||
// Cleanup: delete the response file after processing
|
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
}
|
}
|
||||||
@@ -181,7 +233,7 @@ async function handleResponse(filePath) {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Handle a text command from Discord.
|
* Handle a text command from Discord.
|
||||||
* Supports special commands (!auto on/off) and general text relay.
|
* ONLY processes commands matching this project's name.
|
||||||
*/
|
*/
|
||||||
async function handleCommand(filePath) {
|
async function handleCommand(filePath) {
|
||||||
try {
|
try {
|
||||||
@@ -194,28 +246,67 @@ async function handleCommand(filePath) {
|
|||||||
if (command.consumed || !command.text) {
|
if (command.consumed || !command.text) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// ★ PROJECT FILTER — only process commands for THIS project
|
||||||
|
const cmdProject = command.project_name || '';
|
||||||
|
if (cmdProject && cmdProject !== projectName) {
|
||||||
|
console.log(`Gravity Bridge [${projectName}]: skipping command for "${cmdProject}"`);
|
||||||
|
return; // Not for us — leave file for the correct Extension instance
|
||||||
|
}
|
||||||
const text = command.text.trim();
|
const text = command.text.trim();
|
||||||
console.log(`Gravity Bridge: command received — "${text.substring(0, 50)}"`);
|
console.log(`Gravity Bridge [${projectName}]: command — "${text.substring(0, 50)}"`);
|
||||||
// Special command: auto-approve toggle
|
// Special command: !stop — cancel AI work
|
||||||
if (text === '!auto on' || text === '!auto off') {
|
if (text === '!stop') {
|
||||||
const enabled = text === '!auto on';
|
try {
|
||||||
await toggleAutoApprove(enabled);
|
await vscode.commands.executeCommand('workbench.action.chat.stop');
|
||||||
// Mark as consumed
|
vscode.window.showWarningMessage(`⏹️ [${projectName}] AI 작업 중지됨`);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
vscode.window.showErrorMessage('AI 중지 명령 실행 실패');
|
||||||
|
}
|
||||||
command.consumed = true;
|
command.consumed = true;
|
||||||
fs.writeFileSync(filePath, JSON.stringify(command, null, 2), 'utf-8');
|
fs.writeFileSync(filePath, JSON.stringify(command, null, 2), 'utf-8');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// General text: type into chat panel
|
// Special command: auto-approve toggle
|
||||||
await vscode.commands.executeCommand('workbench.action.chat.open');
|
if (text === '!auto on' || text === '!auto off') {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
const enabled = text === '!auto on';
|
||||||
const oldClipboard = await vscode.env.clipboard.readText();
|
await toggleAutoApprove(enabled);
|
||||||
await vscode.env.clipboard.writeText(command.text);
|
command.consumed = true;
|
||||||
await vscode.commands.executeCommand('editor.action.clipboardPasteAction');
|
fs.writeFileSync(filePath, JSON.stringify(command, null, 2), 'utf-8');
|
||||||
await vscode.env.clipboard.writeText(oldClipboard);
|
return;
|
||||||
// Mark as consumed
|
}
|
||||||
|
// General text: send directly to Antigravity agent panel
|
||||||
|
try {
|
||||||
|
await vscode.commands.executeCommand('antigravity.sendPromptToAgentPanel', command.text);
|
||||||
|
console.log(`Gravity Bridge: ✅ sent via sendPromptToAgentPanel`);
|
||||||
|
}
|
||||||
|
catch (e1) {
|
||||||
|
console.log(`Gravity Bridge: sendPromptToAgentPanel failed: ${e1}`);
|
||||||
|
// Fallback: try sendChatActionMessage
|
||||||
|
try {
|
||||||
|
await vscode.commands.executeCommand('antigravity.sendChatActionMessage', command.text);
|
||||||
|
console.log(`Gravity Bridge: ✅ sent via sendChatActionMessage`);
|
||||||
|
}
|
||||||
|
catch (e2) {
|
||||||
|
console.log(`Gravity Bridge: sendChatActionMessage failed: ${e2}`);
|
||||||
|
// Last resort: focus panel + clipboard paste
|
||||||
|
try {
|
||||||
|
await vscode.commands.executeCommand('antigravity.agentPanel.focus');
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300));
|
||||||
|
const oldClip = await vscode.env.clipboard.readText();
|
||||||
|
await vscode.env.clipboard.writeText(command.text);
|
||||||
|
await vscode.commands.executeCommand('editor.action.clipboardPasteAction');
|
||||||
|
await vscode.env.clipboard.writeText(oldClip);
|
||||||
|
console.log('Gravity Bridge: clipboard paste fallback');
|
||||||
|
}
|
||||||
|
catch (e3) {
|
||||||
|
console.error('Gravity Bridge: all methods failed', e3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Always mark as consumed
|
||||||
command.consumed = true;
|
command.consumed = true;
|
||||||
fs.writeFileSync(filePath, JSON.stringify(command, null, 2), 'utf-8');
|
fs.writeFileSync(filePath, JSON.stringify(command, null, 2), 'utf-8');
|
||||||
vscode.window.showInformationMessage(`📨 Discord input: ${command.text.substring(0, 50)}...`);
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error('Gravity Bridge: error handling command', err);
|
console.error('Gravity Bridge: error handling command', err);
|
||||||
@@ -227,26 +318,21 @@ async function handleCommand(filePath) {
|
|||||||
async function toggleAutoApprove(enabled) {
|
async function toggleAutoApprove(enabled) {
|
||||||
const config = vscode.workspace.getConfiguration();
|
const config = vscode.workspace.getConfiguration();
|
||||||
try {
|
try {
|
||||||
// Core auto-approve settings
|
|
||||||
await config.update('chat.tools.autoApprove', enabled, vscode.ConfigurationTarget.Global);
|
await config.update('chat.tools.autoApprove', enabled, vscode.ConfigurationTarget.Global);
|
||||||
await config.update('chat.agent.autoApprove', enabled, vscode.ConfigurationTarget.Global);
|
await config.update('chat.agent.autoApprove', enabled, vscode.ConfigurationTarget.Global);
|
||||||
// Terminal auto-execution
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
await config.update('chat.tools.terminal.enableAutoApprove', true, vscode.ConfigurationTarget.Global);
|
await config.update('chat.tools.terminal.enableAutoApprove', true, vscode.ConfigurationTarget.Global);
|
||||||
}
|
}
|
||||||
// File edits auto-accept
|
|
||||||
await config.update('autoAcceptV2.autoAcceptFileEdits', enabled, vscode.ConfigurationTarget.Global);
|
await config.update('autoAcceptV2.autoAcceptFileEdits', enabled, vscode.ConfigurationTarget.Global);
|
||||||
// Update status bar
|
|
||||||
statusBar.text = enabled
|
statusBar.text = enabled
|
||||||
? '$(radio-tower) Bridge: Auto ✅'
|
? `$(radio-tower) ${projectName}: Auto ✅`
|
||||||
: '$(radio-tower) Bridge: Manual 🔒';
|
: `$(radio-tower) ${projectName}: Manual 🔒`;
|
||||||
const mode = enabled ? '자동 승인 ON 🟢' : '수동 승인 OFF 🔴';
|
const mode = enabled ? '자동 승인 ON 🟢' : '수동 승인 OFF 🔴';
|
||||||
vscode.window.showInformationMessage(`Gravity Bridge: ${mode}`);
|
vscode.window.showInformationMessage(`Gravity Bridge (${projectName}): ${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`);
|
const statusPath = path.join(bridgePath, 'commands', `auto-status-${Date.now()}.json`);
|
||||||
fs.writeFileSync(statusPath, JSON.stringify({
|
fs.writeFileSync(statusPath, JSON.stringify({
|
||||||
id: `auto-status-${Date.now()}`,
|
id: `auto-status-${Date.now()}`,
|
||||||
|
project_name: projectName,
|
||||||
text: `[SYSTEM] Auto-approve: ${enabled ? 'ON' : 'OFF'}`,
|
text: `[SYSTEM] Auto-approve: ${enabled ? 'ON' : 'OFF'}`,
|
||||||
timestamp: Date.now() / 1000,
|
timestamp: Date.now() / 1000,
|
||||||
consumed: true,
|
consumed: true,
|
||||||
@@ -255,34 +341,23 @@ async function toggleAutoApprove(enabled) {
|
|||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error('Gravity Bridge: failed to toggle auto-approve', 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() {
|
async function simulateApproval() {
|
||||||
try {
|
try {
|
||||||
// Strategy 1: Try executing the accept command if available
|
|
||||||
await vscode.commands.executeCommand('workbench.action.acceptSelectedCodeAction');
|
await vscode.commands.executeCommand('workbench.action.acceptSelectedCodeAction');
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
// Strategy 2: Send Enter key via type command
|
|
||||||
try {
|
try {
|
||||||
await vscode.commands.executeCommand('type', { text: '\n' });
|
await vscode.commands.executeCommand('type', { text: '\n' });
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
// Strategy 3: Focus terminal and send Enter
|
|
||||||
await vscode.commands.executeCommand('workbench.action.terminal.focus');
|
await vscode.commands.executeCommand('workbench.action.terminal.focus');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Simulate rejection — try multiple strategies.
|
|
||||||
*/
|
|
||||||
async function simulateRejection() {
|
async function simulateRejection() {
|
||||||
try {
|
try {
|
||||||
// Strategy 1: Escape key
|
|
||||||
await vscode.commands.executeCommand('workbench.action.closeQuickOpen');
|
await vscode.commands.executeCommand('workbench.action.closeQuickOpen');
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -290,14 +365,12 @@ async function simulateRejection() {
|
|||||||
await vscode.commands.executeCommand('cancelSelection');
|
await vscode.commands.executeCommand('cancelSelection');
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
// Fallback: just notify
|
|
||||||
console.log('Gravity Bridge: rejection sent but no active dialog found');
|
console.log('Gravity Bridge: rejection sent but no active dialog found');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Manual approve/reject from command palette.
|
* Manual approve/reject from command palette.
|
||||||
* Writes a pending request for testing purposes.
|
|
||||||
*/
|
*/
|
||||||
function handleManualAction(approved) {
|
function handleManualAction(approved) {
|
||||||
const requestId = `manual-${Date.now()}`;
|
const requestId = `manual-${Date.now()}`;
|
||||||
@@ -318,6 +391,7 @@ function handleManualAction(approved) {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Write a pending approval request to bridge/pending/ for Discord bot to pick up.
|
* Write a pending approval request to bridge/pending/ for Discord bot to pick up.
|
||||||
|
* Includes project_name for correct channel routing.
|
||||||
*/
|
*/
|
||||||
function writePendingApproval(conversationId, command, description) {
|
function writePendingApproval(conversationId, command, description) {
|
||||||
const requestId = `req-${Date.now()}`;
|
const requestId = `req-${Date.now()}`;
|
||||||
@@ -325,6 +399,7 @@ function writePendingApproval(conversationId, command, description) {
|
|||||||
const request = {
|
const request = {
|
||||||
request_id: requestId,
|
request_id: requestId,
|
||||||
conversation_id: conversationId,
|
conversation_id: conversationId,
|
||||||
|
project_name: projectName, // ★ Project routing
|
||||||
command: command,
|
command: command,
|
||||||
description: description,
|
description: description,
|
||||||
timestamp: Date.now() / 1000,
|
timestamp: Date.now() / 1000,
|
||||||
@@ -333,9 +408,249 @@ function writePendingApproval(conversationId, command, description) {
|
|||||||
};
|
};
|
||||||
fs.writeFileSync(pendingPath, JSON.stringify(request, null, 2), 'utf-8');
|
fs.writeFileSync(pendingPath, JSON.stringify(request, null, 2), 'utf-8');
|
||||||
sentPendingIds.add(requestId);
|
sentPendingIds.add(requestId);
|
||||||
console.log(`Gravity Bridge: pending approval written — ${requestId}`);
|
console.log(`Gravity Bridge [${projectName}]: pending approval — ${requestId}`);
|
||||||
return requestId;
|
return requestId;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Register a conversation → project mapping in bridge/register/.
|
||||||
|
* The bot reads these files to route brain events to the correct channel.
|
||||||
|
*/
|
||||||
|
function registerConversation(conversationId) {
|
||||||
|
const registerDir = path.join(bridgePath, 'register');
|
||||||
|
if (!fs.existsSync(registerDir)) {
|
||||||
|
fs.mkdirSync(registerDir, { recursive: true });
|
||||||
|
}
|
||||||
|
const filePath = path.join(registerDir, `${conversationId}.json`);
|
||||||
|
const data = {
|
||||||
|
conversation_id: conversationId,
|
||||||
|
project_name: projectName,
|
||||||
|
timestamp: Date.now() / 1000,
|
||||||
|
};
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||||
|
console.log(`Gravity Bridge [${projectName}]: registered ${conversationId.substring(0, 8)}`);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Read the title (first # heading) from a conversation's task.md or implementation_plan.md.
|
||||||
|
*/
|
||||||
|
function getConversationTitle(convDir) {
|
||||||
|
for (const fname of ['task.md', 'implementation_plan.md']) {
|
||||||
|
const fpath = path.join(convDir, fname);
|
||||||
|
if (fs.existsSync(fpath)) {
|
||||||
|
try {
|
||||||
|
const lines = fs.readFileSync(fpath, 'utf-8').split('\n').slice(0, 5);
|
||||||
|
for (const line of lines) {
|
||||||
|
const match = line.match(/^#\s+(.+)/);
|
||||||
|
if (match) {
|
||||||
|
return match[1].trim().substring(0, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Manual connect: scan brain/ for recent conversations and let user pick.
|
||||||
|
* Shows task.md titles for readability. Offers auto-connect for new projects.
|
||||||
|
*/
|
||||||
|
async function connectSession() {
|
||||||
|
const brainPath = path.join(os.homedir(), '.gemini', 'antigravity', 'brain');
|
||||||
|
if (!fs.existsSync(brainPath)) {
|
||||||
|
vscode.window.showErrorMessage('Brain 디렉토리를 찾을 수 없습니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Get conversation dirs sorted by modification time (newest first)
|
||||||
|
const dirs = fs.readdirSync(brainPath)
|
||||||
|
.filter(d => {
|
||||||
|
const fullPath = path.join(brainPath, d);
|
||||||
|
return fs.statSync(fullPath).isDirectory() && d.includes('-');
|
||||||
|
})
|
||||||
|
.map(d => {
|
||||||
|
const fullPath = path.join(brainPath, d);
|
||||||
|
return {
|
||||||
|
name: d,
|
||||||
|
mtime: fs.statSync(fullPath).mtimeMs,
|
||||||
|
title: getConversationTitle(fullPath),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.mtime - a.mtime)
|
||||||
|
.slice(0, 10);
|
||||||
|
// Build QuickPick items
|
||||||
|
const items = [];
|
||||||
|
// Always offer auto-connect option first
|
||||||
|
items.push({
|
||||||
|
label: '$(sync) 새 대화 자동 연결',
|
||||||
|
description: '다음에 시작하는 대화가 자동으로 이 프로젝트에 연결됩니다',
|
||||||
|
detail: `프로젝트: ${projectName}`,
|
||||||
|
});
|
||||||
|
// Add conversation items with titles
|
||||||
|
for (const d of dirs) {
|
||||||
|
const titleLabel = d.title || '(제목 없음)';
|
||||||
|
const timeStr = new Date(d.mtime).toLocaleString();
|
||||||
|
items.push({
|
||||||
|
label: `$(comment-discussion) ${titleLabel}`,
|
||||||
|
description: d.name.substring(0, 8),
|
||||||
|
detail: `${d.name} · ${timeStr}`,
|
||||||
|
convId: d.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const selected = await vscode.window.showQuickPick(items, {
|
||||||
|
placeHolder: `프로젝트 "${projectName}"에 연결할 세션을 선택하세요`,
|
||||||
|
});
|
||||||
|
if (!selected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!('convId' in selected) || !selected.convId) {
|
||||||
|
// Auto-connect mode
|
||||||
|
vscode.window.showInformationMessage(`🔄 ${projectName}: 다음 대화가 자동으로 연결됩니다`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
registerConversation(selected.convId);
|
||||||
|
vscode.window.showInformationMessage(`✅ ${selected.description} → ${projectName} 연결됨`);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Auto-watch brain/ for new conversation directories → auto-register.
|
||||||
|
*/
|
||||||
|
function watchBrainForNewSessions() {
|
||||||
|
const brainPath = path.join(os.homedir(), '.gemini', 'antigravity', 'brain');
|
||||||
|
if (!fs.existsSync(brainPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Track known dirs
|
||||||
|
const knownDirs = new Set(fs.readdirSync(brainPath).filter(d => fs.statSync(path.join(brainPath, d)).isDirectory()));
|
||||||
|
try {
|
||||||
|
fs.watch(brainPath, { persistent: false }, (eventType, filename) => {
|
||||||
|
if (!filename || !filename.includes('-')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fullPath = path.join(brainPath, filename);
|
||||||
|
// Check if it's a new directory
|
||||||
|
if (!knownDirs.has(filename) && fs.existsSync(fullPath) &&
|
||||||
|
fs.statSync(fullPath).isDirectory()) {
|
||||||
|
knownDirs.add(filename);
|
||||||
|
registerConversation(filename);
|
||||||
|
console.log(`Gravity Bridge [${projectName}]: auto-registered new session ${filename.substring(0, 8)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`Gravity Bridge [${projectName}]: watching brain/ for new sessions`);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error('Gravity Bridge: failed to watch brain dir', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Monitor text document changes for chat panel content.
|
||||||
|
* VS Code chat documents have special URI schemes (vscode-chat-response, etc.).
|
||||||
|
* We capture significant changes and relay to Discord.
|
||||||
|
*/
|
||||||
|
let lastChatContent = '';
|
||||||
|
let chatDebounceTimer = null;
|
||||||
|
function handleChatDocumentChange(event) {
|
||||||
|
const doc = event.document;
|
||||||
|
const scheme = doc.uri.scheme;
|
||||||
|
// Log ALL schemes to discover chat-related ones (debug mode)
|
||||||
|
if (scheme !== 'file' && scheme !== 'git' && scheme !== 'output' &&
|
||||||
|
scheme !== 'vscode-userdata' && scheme !== 'untitled') {
|
||||||
|
console.log(`Gravity Bridge [${projectName}]: doc change scheme="${scheme}" uri="${doc.uri.toString().substring(0, 80)}"`);
|
||||||
|
}
|
||||||
|
// Capture chat-related documents
|
||||||
|
// Known chat schemes: vscode-chat-response, vscode-copilot-chat, etc.
|
||||||
|
const isChatDoc = scheme.includes('chat') || scheme.includes('copilot') ||
|
||||||
|
scheme.includes('notebook') || doc.uri.path.includes('chat');
|
||||||
|
if (!isChatDoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const content = doc.getText();
|
||||||
|
if (!content || content === lastChatContent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Debounce: wait 2s for content to stabilize (AI streams text)
|
||||||
|
if (chatDebounceTimer) {
|
||||||
|
clearTimeout(chatDebounceTimer);
|
||||||
|
}
|
||||||
|
chatDebounceTimer = setTimeout(() => {
|
||||||
|
const finalContent = doc.getText();
|
||||||
|
if (finalContent && finalContent !== lastChatContent && finalContent.length > 20) {
|
||||||
|
lastChatContent = finalContent;
|
||||||
|
writeChatSnapshot(finalContent);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Write a chat content snapshot to bridge for the bot to relay.
|
||||||
|
*/
|
||||||
|
function writeChatSnapshot(content) {
|
||||||
|
const snapshotDir = path.join(bridgePath, 'chat_snapshots');
|
||||||
|
if (!fs.existsSync(snapshotDir)) {
|
||||||
|
fs.mkdirSync(snapshotDir, { recursive: true });
|
||||||
|
}
|
||||||
|
const id = `chat-${Date.now()}`;
|
||||||
|
const filePath = path.join(snapshotDir, `${id}.json`);
|
||||||
|
const data = {
|
||||||
|
id,
|
||||||
|
project_name: projectName,
|
||||||
|
content: content.substring(0, 4000), // Limit size
|
||||||
|
timestamp: Date.now() / 1000,
|
||||||
|
};
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||||
|
console.log(`Gravity Bridge [${projectName}]: chat snapshot written (${content.length} chars)`);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @bridge Chat Participant handler.
|
||||||
|
* Reads conversation history and sends to Discord via bridge.
|
||||||
|
*/
|
||||||
|
const bridgeChatHandler = async (request, context, stream, token) => {
|
||||||
|
const command = request.prompt.trim().toLowerCase();
|
||||||
|
if (command === 'stop' || command === '중지') {
|
||||||
|
// Cancel current AI work
|
||||||
|
try {
|
||||||
|
await vscode.commands.executeCommand('workbench.action.chat.stop');
|
||||||
|
stream.markdown('⏹️ AI 작업 중지 요청을 보냈습니다.');
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
stream.markdown('⚠️ 중지 명령을 실행할 수 없습니다.');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Collect conversation history
|
||||||
|
const historyLines = [];
|
||||||
|
historyLines.push(`# 대화 히스토리 (${projectName})\n`);
|
||||||
|
for (const entry of context.history) {
|
||||||
|
if (entry instanceof vscode.ChatRequestTurn) {
|
||||||
|
historyLines.push(`## 👤 사용자\n${entry.prompt}\n`);
|
||||||
|
}
|
||||||
|
else if (entry instanceof vscode.ChatResponseTurn) {
|
||||||
|
let responseText = '';
|
||||||
|
for (const part of entry.response) {
|
||||||
|
if (part instanceof vscode.ChatResponseMarkdownPart) {
|
||||||
|
responseText += part.value.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (responseText) {
|
||||||
|
historyLines.push(`## 🤖 AI\n${responseText}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (historyLines.length <= 1) {
|
||||||
|
stream.markdown('대화 히스토리가 비어있습니다. AI와 대화를 먼저 진행한 후 `@bridge`를 호출하세요.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Write to bridge for Discord relay
|
||||||
|
const fullHistory = historyLines.join('\n');
|
||||||
|
const cmdId = `bridge-history-${Date.now()}`;
|
||||||
|
const cmdPath = path.join(bridgePath, 'commands', `${cmdId}.json`);
|
||||||
|
const data = {
|
||||||
|
id: cmdId,
|
||||||
|
project_name: projectName,
|
||||||
|
text: `[HISTORY]\n${fullHistory}`,
|
||||||
|
timestamp: Date.now() / 1000,
|
||||||
|
consumed: false,
|
||||||
|
};
|
||||||
|
fs.writeFileSync(cmdPath, JSON.stringify(data, null, 2), 'utf-8');
|
||||||
|
stream.markdown(`✅ 대화 히스토리 (${context.history.length}개 턴)를 Discord에 전송했습니다.`);
|
||||||
|
console.log(`Gravity Bridge [${projectName}]: sent ${context.history.length} turns to Discord`);
|
||||||
|
};
|
||||||
function deactivate() {
|
function deactivate() {
|
||||||
stopBridge();
|
stopBridge();
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
39
extension/package-lock.json
generated
39
extension/package-lock.json
generated
@@ -1,19 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"antigravity-sdk": "^1.6.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.0.0",
|
"@types/node": "^20.0.0",
|
||||||
"@types/vscode": "^1.80.0",
|
"@types/vscode": "^1.100.0",
|
||||||
"typescript": "^5.3.0"
|
"typescript": "^5.3.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.80.0"
|
"vscode": "^1.100.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
@@ -27,10 +30,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/vscode": {
|
"node_modules/@types/vscode": {
|
||||||
"version": "1.109.0",
|
"version": "1.100.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.109.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.100.0.tgz",
|
||||||
"integrity": "sha512-0Pf95rnwEIwDbmXGC08r0B4TQhAbsHQ5UyTIgVgoieDe4cOnf92usuR5dEczb6bTKEp7ziZH4TV1TRGPPCExtw==",
|
"integrity": "sha512-4uNyvzHoraXEeCamR3+fzcBlh7Afs4Ifjs4epINyUX/jvdk0uzLnwiDY35UKDKnkCHP5Nu3dljl2H8lR6s+rQw==",
|
||||||
"dev": true,
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/antigravity-sdk": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/antigravity-sdk/-/antigravity-sdk-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-VdaLlSujbr+9WNCxs57N8nkuWdSmUT4tD5BsO1XGjAKD6f1aPMCtqeMBYP6iWHRUGZZjtGvSQaUGBt/WR/9iAA==",
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"dependencies": {
|
||||||
|
"sql.js": "^1.14.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/vscode": "^1.85.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sql.js": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.1.tgz",
|
||||||
|
"integrity": "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
|
|||||||
@@ -71,5 +71,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"antigravity-sdk": "^1.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user