feat(bot,bridge): P1 !auto 토글 자동승인 + P2 BridgeTransport 추상화 #task-304 #task-305

P1: !auto 토글 (bot.py + extension.ts)
- auto_approve_projects set으로 프로젝트별 상태 관리
- !auto → on/off 토글, pending 자동 승인 + 🤖 자동 승인됨 embed
- Extension step_probe에서 autoApproveEnabled 시 직접 tryApprovalStrategies

P2: BridgeTransport 추상화 (bridge.py)
- BridgeTransport ABC + LocalTransport (기존 동작 100% 호환)
- RemoteTransport 스켈레톤 (multi-PC 대비)
- config.py BOT_MODE/REMOTE_BRIDGE_URL, main.py transport 주입

docs: usage-guide.md + tech-stack.md Python 경로 기록
This commit is contained in:
Variet Worker
2026-03-11 19:25:40 +09:00
parent 1696a2976b
commit c1303999cf
8 changed files with 434 additions and 106 deletions

62
bot.py
View File

@@ -179,6 +179,7 @@ class GravityBot(commands.Bot):
self.bridge = BridgeProtocol()
self.session_category: discord.CategoryChannel | None = None
self.guild: discord.Guild | None = None
self.auto_approve_projects: set[str] = set() # projects with auto-approve enabled
@staticmethod
def _make_channel_name(project_name: str) -> str:
@@ -211,12 +212,18 @@ class GravityBot(commands.Bot):
)
@self.tree.command(name="auto", description="자동 승인 토글")
async def slash_auto(interaction: discord.Interaction, mode: str):
async def slash_auto(interaction: discord.Interaction):
project = self.channel_to_project.get(interaction.channel_id)
if not project:
await interaction.response.send_message("⚠️ 프로젝트 채널이 아닙니다.", ephemeral=True)
return
enabled = mode.lower() in ("on", "true", "1")
# Toggle
if project in self.auto_approve_projects:
self.auto_approve_projects.discard(project)
enabled = False
else:
self.auto_approve_projects.add(project)
enabled = True
self.bridge.write_command(project, f"!auto {'on' if enabled else 'off'}", project_name=project)
emoji = "🟢" if enabled else "🔴"
await interaction.response.send_message(
@@ -521,6 +528,35 @@ class GravityBot(commands.Bot):
if req.discord_message_id != 0:
continue
# Learn project mapping from pending approval
project = req.project_name or Config.PROJECT_NAME
if req.conversation_id and req.conversation_id != '__global__':
self.conv_to_project[req.conversation_id] = project
# ── Auto-approve: if project has auto enabled, approve immediately ──
if project in self.auto_approve_projects:
self._sent_approval_ids.add(req.request_id)
# Write auto-approve response for Extension
self.bridge.write_response(UserResponse(
request_id=req.request_id,
approved=True,
button_index=0, # first button (Allow Once / Run)
step_type=getattr(req, 'step_type', ''),
project_name=project,
))
# Show compact auto-approved embed in Discord
channel = await self._get_channel(project)
if channel:
embed = discord.Embed(
title="🤖 자동 승인됨",
description=f"```\n{req.command[:500]}\n```",
color=discord.Color.green(),
)
embed.set_footer(text=f"auto-approve | {req.request_id[:12]}")
await channel.send(embed=embed)
logger.info(f"Auto-approved: {req.request_id[:12]} project={project}")
continue
# Defer short-command pendings (e.g. "Run") by 4 cycles (~12s)
# to give step_probe time to merge detailed command info
# (step_probe MERGE happens ~10s after pending creation)
@@ -540,11 +576,6 @@ class GravityBot(commands.Bot):
# Clean up defer tracking
self._deferred_ids.pop(req.request_id, None)
# Learn project mapping from pending approval
project = req.project_name or Config.PROJECT_NAME
if req.conversation_id and req.conversation_id != '__global__':
self.conv_to_project[req.conversation_id] = project
channel = await self._get_channel(project)
if channel:
self._sent_approval_ids.add(req.request_id)
@@ -732,17 +763,22 @@ class GravityBot(commands.Bot):
await message.channel.send(embed=embed)
return
# Special command: !auto on/off
if text in ("!auto on", "!auto off"):
self.bridge.write_command(project, text, project_name=project)
enabled = text == "!auto on"
# Special command: !auto — toggle auto-approve
if text == "!auto":
# Toggle per-project auto-approve
if project in self.auto_approve_projects:
self.auto_approve_projects.discard(project)
enabled = False
else:
self.auto_approve_projects.add(project)
enabled = True
self.bridge.write_command(project, f"!auto {'on' if enabled else 'off'}", project_name=project)
emoji = "🟢" if enabled else "🔴"
mode = "자동 승인" if enabled else "수동 승인"
embed = discord.Embed(
title=f"{emoji} {mode} 모드",
description=f"프로젝트: **{project}**\n"
f"`chat.tools.autoApprove = {enabled}`\n"
f"`chat.agent.autoApprove = {enabled}`",
f"모든 승인 요청이 {'자동으로 승인됩니다' if enabled else '수동 확인이 필요합니다'}",
color=discord.Color.green() if enabled else discord.Color.red(),
)
await message.channel.send(embed=embed)