diff --git a/bot.py b/bot.py index 107d3c3..b202a82 100644 --- a/bot.py +++ b/bot.py @@ -102,8 +102,9 @@ class ApprovalView(discord.ui.View): await self.hub.send_response_to_pending_owner(self.request.request_id, { "type": "response", "data": response_data, }) - # File bridge (fallback — local Extensions) - self.bridge.write_response(UserResponse(**response_data)) + else: + # File bridge (fallback — only when Hub is unavailable) + self.bridge.write_response(UserResponse(**response_data)) embed = interaction.message.embeds[0] if interaction.message.embeds else None if embed: color = discord.Color.red() if is_reject else discord.Color.green() @@ -133,7 +134,8 @@ class ApprovalView(discord.ui.View): await self.hub.send_response_to_pending_owner(self.request.request_id, { "type": "response", "data": response_data, }) - self.bridge.write_response(UserResponse(**response_data)) + else: + self.bridge.write_response(UserResponse(**response_data)) embed = interaction.message.embeds[0] if interaction.message.embeds else None if embed: embed.color = discord.Color.green() @@ -158,7 +160,8 @@ class ApprovalView(discord.ui.View): await self.hub.send_response_to_pending_owner(self.request.request_id, { "type": "response", "data": response_data, }) - self.bridge.write_response(UserResponse(**response_data)) + else: + self.bridge.write_response(UserResponse(**response_data)) embed = interaction.message.embeds[0] if interaction.message.embeds else None if embed: embed.color = discord.Color.red() @@ -213,7 +216,10 @@ class GravityBot(commands.Bot): def _write_command(self, project: str, text: str, *, target_instance: int | None = None, **kwargs): - """Write command to bridge AND push to gateway/hub. + """Write command to Extension via Hub WS (primary) or file bridge (fallback). + + When Hub is connected, ONLY use WS to prevent duplicate delivery. + File bridge + Gateway are legacy fallbacks for when Hub is unavailable. Args: target_instance: If set, send only to this instance number (via Hub). @@ -224,7 +230,7 @@ class GravityBot(commands.Bot): "project_name": kwargs.get('project_name', project), } - # Hub route (preferred if available) + # Hub route (primary — skip file bridge to prevent double delivery) if self.hub: import time as _time cmd_data["id"] = str(int(_time.time() * 1000)) @@ -237,8 +243,9 @@ class GravityBot(commands.Bot): asyncio.create_task( self.hub.broadcast_to_project(project, msg) ) + return # ← WS sent, skip file bridge - # Legacy routes (file bridge + gateway HTTP) + # Legacy fallback (file bridge + gateway HTTP) — only when Hub is unavailable self.bridge.write_command(project, text, **kwargs) if self.gateway: import time as _time diff --git a/extension/src/extension.ts b/extension/src/extension.ts index cc61b96..1f70ddf 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -405,8 +405,11 @@ export async function activate(context: vscode.ExtensionContext) { }, onCommand: (data: WSCommandData) => { logToFile(`[WS-CMD] ${data.text?.substring(0, 50)}`); - // Process command directly (same logic as processCommandFile) - _handleWSCommand(data); + handleWSCommand({ + bridgePath, projectName, sdk, autoApproveEnabled, logToFile, + onAutoApproveChanged: (enabled: boolean) => { autoApproveEnabled = enabled; }, + recentDiscordSentTexts, + }, data); }, onInstanceUpdate: (count, instances) => { logToFile(`[WS-INSTANCE] ${count} active instances`); @@ -609,41 +612,3 @@ export function deactivate() { try { sdk.dispose(); } catch { } } } - -// ─── WS Command Handler ─── - -function _handleWSCommand(data: WSCommandData) { - const text = data.text || ''; - if (!text) return; - - // Project filtering (WS already routes by project, but double-check) - if (data.project_name && data.project_name !== projectName) { - logToFile(`[WS-CMD] Ignoring command for ${data.project_name} (we are ${projectName})`); - return; - } - - if (text === '!stop') { - logToFile('[WS-CMD] !stop — cancelling AG task'); - if (sdk) { - try { sdk.cascade.cancelCurrentTask(); } catch { } - } - return; - } - - if (text.startsWith('!auto')) { - const parts = text.split(' '); - autoApproveEnabled = parts[1] !== 'off'; - logToFile(`[WS-CMD] auto_approve=${autoApproveEnabled}`); - return; - } - - // General text → send as user message to AG - logToFile(`[WS-CMD] Sending text to AG: ${text.substring(0, 80)}`); - if (sdk) { - try { - sdk.cascade.sendPrompt(text); - } catch (e: any) { - logToFile(`[WS-CMD] SDK sendPrompt error: ${e.message}`); - } - } -}