diff --git a/bot.py b/bot.py index df7c9a2..a2ba2f7 100644 --- a/bot.py +++ b/bot.py @@ -125,7 +125,7 @@ class GravityBot(commands.Bot): self.session_channels: dict[str, discord.TextChannel] = {} self.session_status_messages: dict[str, int] = {} self.session_names: dict[str, str] = {} - self._channel_locks: dict[str, asyncio.Lock] = {} + self._channel_create_lock = asyncio.Lock() # SINGLE global lock self.bridge = BridgeProtocol() self.session_category: discord.CategoryChannel | None = None self.guild: discord.Guild | None = None @@ -156,23 +156,20 @@ class GravityBot(commands.Bot): logger.error("No permission to create category!") return - # ONLY reconnect to existing Discord channels (NO new channel creation) + # ONLY reconnect to existing Discord channels (NO new creation) await self._reconnect_existing_channels() async def _reconnect_existing_channels(self): - """Scan existing Discord channels and map them to conversation IDs. - Does NOT create any new channels.""" + """Scan existing Discord channels and map them to conversation IDs.""" if not self.session_category: return count = 0 for ch in self.session_category.text_channels: if ch.topic and "Antigravity Session:" in ch.topic: - # Extract conversation ID from topic conv_id = ch.topic.replace("Antigravity Session:", "").strip() if conv_id: self.session_channels[conv_id] = ch - # Recover last task embed await self._recover_task_message(ch, conv_id) count += 1 @@ -181,7 +178,6 @@ class GravityBot(commands.Bot): async def _recover_task_message( self, channel: discord.TextChannel, conversation_id: str ): - """Find last task embed in channel to reuse for editing.""" if conversation_id in self.session_status_messages: return try: @@ -199,43 +195,37 @@ class GravityBot(commands.Bot): async def _ensure_channel( self, conversation_id: str, project_name: str ) -> discord.TextChannel: - """Get or create a channel (thread-safe, single creation per session).""" + """Get or create a channel. Uses GLOBAL lock and NAME-based lookup.""" + # Fast path: already mapped if conversation_id in self.session_channels: - ch = self.session_channels[conversation_id] - # Rename back from "closed-" if needed - expected = f"{Config.CHANNEL_PREFIX}-{project_name}".lower() - if ch.name.startswith("closed-") and ch.name != expected: - try: - await ch.edit(name=f"{Config.CHANNEL_PREFIX}-{project_name}") - except discord.errors.Forbidden: - pass - return ch + return self.session_channels[conversation_id] - # Lock per conversation to prevent duplicates - if conversation_id not in self._channel_locks: - self._channel_locks[conversation_id] = asyncio.Lock() - - async with self._channel_locks[conversation_id]: - # Double-check + async with self._channel_create_lock: + # Double-check after lock if conversation_id in self.session_channels: return self.session_channels[conversation_id] channel_name = f"{Config.CHANNEL_PREFIX}-{project_name}" + target_name = channel_name.lower().replace(" ", "-") - # Check if channel exists but wasn't mapped yet + # Fetch FRESH channel list from Discord API (not cached) if self.session_category: - for ch in self.session_category.text_channels: - if ch.topic and conversation_id in ch.topic: + category = await self.guild.fetch_channel(self.session_category.id) + for ch in category.text_channels: + # Match by NAME (handles different conv IDs → same project) + if ch.name == target_name or (ch.topic and conversation_id in ch.topic): self.session_channels[conversation_id] = ch self.session_names[conversation_id] = project_name + # Rename from closed- if needed if ch.name.startswith("closed-"): try: await ch.edit(name=channel_name) except discord.errors.Forbidden: pass + logger.info(f"Reusing existing channel #{ch.name}") return ch - # Create new channel + # Create new channel (only if truly no match found) try: channel = await self.guild.create_text_channel( name=channel_name,