From 98bb037c810f6972876e631301c86e1ad9176d60 Mon Sep 17 00:00:00 2001 From: CD Date: Sat, 7 Mar 2026 14:15:53 +0900 Subject: [PATCH] feat: session connect command + auto-registration + bridge/register protocol --- bot.py | 26 +++++++++ extension/package.json | 4 ++ extension/src/extension.ts | 107 ++++++++++++++++++++++++++++++++++++- 3 files changed, 136 insertions(+), 1 deletion(-) diff --git a/bot.py b/bot.py index f36155a..22ea0f8 100644 --- a/bot.py +++ b/bot.py @@ -140,12 +140,38 @@ class GravityBot(commands.Bot): # Discover existing project channels await self._discover_channels() + # Load conversation → project registrations from Extension + self._load_registrations() + # Open the gate self._ready_event.set() logger.info("Ready gate opened — event processing enabled") # ─── Channel Management ────────────────────────────────────────── + def _load_registrations(self): + """Read bridge/register/ to learn conversation → project mappings.""" + register_dir = self.bridge.bridge_dir / "register" + if not register_dir.exists(): + return + + count = 0 + for f in register_dir.glob("*.json"): + try: + data = json.loads(f.read_text(encoding="utf-8-sig")) + conv_id = data.get("conversation_id", "") + project = data.get("project_name", "") + if conv_id and project: + self.conv_to_project[conv_id] = project + count += 1 + except (json.JSONDecodeError, OSError): + pass + + if count: + logger.info(f"Loaded {count} conversation→project registrations") + + # ─── Channel Management ────────────────────────────────────────── + async def _discover_channels(self): """Find existing project channels via Discord API (not cache).""" all_channels = await self.guild.fetch_channels() diff --git a/extension/package.json b/extension/package.json index 6642ef0..7b96704 100644 --- a/extension/package.json +++ b/extension/package.json @@ -40,6 +40,10 @@ { "command": "gravityBridge.reject", "title": "Gravity Bridge: Reject Pending" + }, + { + "command": "gravityBridge.connect", + "title": "Gravity Bridge: Connect Session" } ], "configuration": { diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 46162a1..9d444cf 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -72,7 +72,7 @@ export function activate(context: vscode.ExtensionContext) { bridgePath = configPath || path.join(os.homedir(), '.gemini', 'antigravity', 'bridge'); // Ensure bridge directories exist - const dirs = ['pending', 'response', 'commands']; + const dirs = ['pending', 'response', 'commands', 'register']; for (const dir of dirs) { const dirPath = path.join(bridgePath, dir); if (!fs.existsSync(dirPath)) { @@ -92,10 +92,14 @@ export function activate(context: vscode.ExtensionContext) { 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)), ); + // Auto-watch brain/ for new conversations → auto-register + watchBrainForNewSessions(); + // Auto-start startBridge(); } @@ -356,6 +360,107 @@ export function writePendingApproval( 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: string) { + 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)}`); +} + +/** + * Manual connect: scan brain/ for recent conversations and let user pick. + */ +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 => ({ + name: d, + mtime: fs.statSync(path.join(brainPath, d)).mtimeMs, + })) + .sort((a, b) => b.mtime - a.mtime) + .slice(0, 10); // Show top 10 + + if (dirs.length === 0) { + vscode.window.showInformationMessage('활성 세션이 없습니다.'); + return; + } + + const items = dirs.map(d => ({ + label: d.name.substring(0, 8), + description: d.name, + detail: `수정: ${new Date(d.mtime).toLocaleString()}`, + convId: d.name, + })); + + const selected = await vscode.window.showQuickPick(items, { + placeHolder: `프로젝트 "${projectName}"에 연결할 세션을 선택하세요`, + }); + + if (selected) { + registerConversation(selected.convId); + vscode.window.showInformationMessage( + `✅ ${selected.label} → ${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); + } +} + export function deactivate() { stopBridge(); }