feat(bot/extension/watcher): Discord 아티팩트 알림 개선 — 파일 첨부 전송, truncation 확대, 동적 .md 감시

This commit is contained in:
Variet Worker
2026-03-13 09:46:56 +09:00
parent 9036f1cefc
commit e5a05e3ac4
10 changed files with 266 additions and 57 deletions

130
bot.py
View File

@@ -491,34 +491,49 @@ class GravityBot(commands.Bot):
event_label = "생성" if event.event_type == EventType.FILE_CREATED else "업데이트"
full_content = event.content.strip()
CHUNK_SIZE = 4000 # Discord embed desc limit is 4096
if not full_content:
full_content = "(빈 파일)"
# Split into chunks for long content
chunks = []
while full_content:
chunks.append(full_content[:CHUNK_SIZE])
full_content = full_content[CHUNK_SIZE:]
FILE_ATTACH_THRESHOLD = 4000 # Above this, send as file attachment
if not chunks:
chunks = ["(빈 파일)"]
if len(full_content) > FILE_ATTACH_THRESHOLD:
# Long content → summary embed + file attachment
# Extract first meaningful paragraph for summary
summary_lines = []
for line in full_content.split('\n'):
if line.strip():
summary_lines.append(line.strip())
if len('\n'.join(summary_lines)) > 300:
break
summary = '\n'.join(summary_lines[:5])
if len(summary) > 500:
summary = summary[:500] + '...'
# First chunk with title
embed = discord.Embed(
title=f"{label} ({event_label}됨)",
description=chunks[0],
color=discord.Color.blue(),
timestamp=datetime.now(timezone.utc),
)
embed.set_footer(text=f"Session: {event.conversation_id[:8]}")
await channel.send(embed=embed)
# Additional chunks if content is long
for i, chunk in enumerate(chunks[1:], 2):
embed = discord.Embed(
title=f"{label} (계속 {i}/{len(chunks)})",
description=chunk,
title=f"{label} ({event_label})",
description=f"{summary}\n\n📎 *전체 내용은 첨부 파일을 확인하세요* ({len(full_content):,}자)",
color=discord.Color.blue(),
timestamp=datetime.now(timezone.utc),
)
embed.set_footer(text=f"Session: {event.conversation_id[:8]}")
# Create in-memory file attachment
import io
file_bytes = full_content.encode('utf-8')
discord_file = discord.File(
io.BytesIO(file_bytes),
filename=event.file_name,
)
await channel.send(embed=embed, file=discord_file)
else:
# Short content → inline embed (original behavior)
embed = discord.Embed(
title=f"{label} ({event_label}됨)",
description=full_content,
color=discord.Color.blue(),
timestamp=datetime.now(timezone.utc),
)
embed.set_footer(text=f"Session: {event.conversation_id[:8]}")
await channel.send(embed=embed)
# ─── Approval Scanner ────────────────────────────────────────────
@@ -823,31 +838,80 @@ class GravityBot(commands.Bot):
data = json.loads(f.read_text(encoding="utf-8-sig"))
project = data.get("project_name", Config.PROJECT_NAME)
content = data.get("content", "")
attached_files = data.get("attached_files", [])
if content:
if content or attached_files:
channel = await self._get_channel(project)
if channel:
# Split long content
CHUNK = 4000
chunks = [content[i:i+CHUNK] for i in range(0, len(content), CHUNK)]
for i, chunk in enumerate(chunks):
title = "💬 AI 대화 내용" if i == 0 else f"💬 (계속 {i+1}/{len(chunks)})"
import io
# ── Send attached files (from Extension's writeChatSnapshotWithFiles) ──
discord_files = []
for af in attached_files:
af_name = af.get("name", "document.md")
af_content = af.get("content", "")
if af_content:
discord_files.append(discord.File(
io.BytesIO(af_content.encode("utf-8")),
filename=af_name,
))
FILE_ATTACH_THRESHOLD = 4000
if len(content) > FILE_ATTACH_THRESHOLD:
# Long chat content → summary embed + file attachment
summary = content[:500].rsplit('\n', 1)[0]
embed = discord.Embed(
title=title,
description=chunk,
title="💬 AI 대화 내용",
description=f"{summary}\n\n📎 *전체 내용은 첨부 파일 참조* ({len(content):,}자)",
color=discord.Color.purple(),
timestamp=datetime.now(timezone.utc),
)
# Add content itself as file attachment
discord_files.append(discord.File(
io.BytesIO(content.encode("utf-8")),
filename="chat_message.md",
))
try:
await channel.send(embed=embed, files=discord_files)
except discord.NotFound:
logger.warning(f"Channel deleted for {project}, re-creating...")
self.project_channels.pop(project, None)
channel = await self._get_channel(project)
if channel:
# Re-create files (discord.File consumed after send)
discord_files2 = []
for af in attached_files:
af_name = af.get("name", "document.md")
af_content = af.get("content", "")
if af_content:
discord_files2.append(discord.File(
io.BytesIO(af_content.encode("utf-8")),
filename=af_name,
))
discord_files2.append(discord.File(
io.BytesIO(content.encode("utf-8")),
filename="chat_message.md",
))
await channel.send(embed=embed, files=discord_files2)
else:
# Short content → inline embed (original)
embed = discord.Embed(
title="💬 AI 대화 내용",
description=content,
color=discord.Color.purple(),
timestamp=datetime.now(timezone.utc),
)
try:
await channel.send(embed=embed)
await channel.send(
embed=embed,
files=discord_files if discord_files else discord.utils.MISSING,
)
except discord.NotFound:
# Channel was deleted — invalidate cache and retry once
logger.warning(f"Channel deleted for {project}, re-creating...")
self.project_channels.pop(project, None)
channel = await self._get_channel(project)
if channel:
await channel.send(embed=embed)
break
f.unlink() # Cleanup
except (json.JSONDecodeError, OSError) as e: