feat: session connect command + auto-registration + bridge/register protocol
This commit is contained in:
26
bot.py
26
bot.py
@@ -140,12 +140,38 @@ class GravityBot(commands.Bot):
|
|||||||
# Discover existing project channels
|
# Discover existing project channels
|
||||||
await self._discover_channels()
|
await self._discover_channels()
|
||||||
|
|
||||||
|
# Load conversation → project registrations from Extension
|
||||||
|
self._load_registrations()
|
||||||
|
|
||||||
# Open the gate
|
# Open the gate
|
||||||
self._ready_event.set()
|
self._ready_event.set()
|
||||||
logger.info("Ready gate opened — event processing enabled")
|
logger.info("Ready gate opened — event processing enabled")
|
||||||
|
|
||||||
# ─── Channel Management ──────────────────────────────────────────
|
# ─── 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):
|
async def _discover_channels(self):
|
||||||
"""Find existing project channels via Discord API (not cache)."""
|
"""Find existing project channels via Discord API (not cache)."""
|
||||||
all_channels = await self.guild.fetch_channels()
|
all_channels = await self.guild.fetch_channels()
|
||||||
|
|||||||
@@ -40,6 +40,10 @@
|
|||||||
{
|
{
|
||||||
"command": "gravityBridge.reject",
|
"command": "gravityBridge.reject",
|
||||||
"title": "Gravity Bridge: Reject Pending"
|
"title": "Gravity Bridge: Reject Pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gravityBridge.connect",
|
||||||
|
"title": "Gravity Bridge: Connect Session"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configuration": {
|
"configuration": {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
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)) {
|
||||||
@@ -92,10 +92,14 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
vscode.commands.registerCommand('gravityBridge.start', startBridge),
|
vscode.commands.registerCommand('gravityBridge.start', startBridge),
|
||||||
vscode.commands.registerCommand('gravityBridge.stop', stopBridge),
|
vscode.commands.registerCommand('gravityBridge.stop', stopBridge),
|
||||||
|
vscode.commands.registerCommand('gravityBridge.connect', connectSession),
|
||||||
vscode.commands.registerCommand('gravityBridge.approve', () => handleManualAction(true)),
|
vscode.commands.registerCommand('gravityBridge.approve', () => handleManualAction(true)),
|
||||||
vscode.commands.registerCommand('gravityBridge.reject', () => handleManualAction(false)),
|
vscode.commands.registerCommand('gravityBridge.reject', () => handleManualAction(false)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Auto-watch brain/ for new conversations → auto-register
|
||||||
|
watchBrainForNewSessions();
|
||||||
|
|
||||||
// Auto-start
|
// Auto-start
|
||||||
startBridge();
|
startBridge();
|
||||||
}
|
}
|
||||||
@@ -356,6 +360,107 @@ export function writePendingApproval(
|
|||||||
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: 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() {
|
export function deactivate() {
|
||||||
stopBridge();
|
stopBridge();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user