Эх сурвалжийг харах

Merge remote-tracking branch 'origin/kenneth/discord-resilience' into kenneth/channels-rollup

Kenneth Lien 5 сар өмнө
parent
commit
87e0f09336

+ 36 - 3
external_plugins/discord/server.ts

@@ -58,6 +58,15 @@ if (!TOKEN) {
 }
 const INBOX_DIR = join(STATE_DIR, 'inbox')
 
+// Last-resort safety net — without these the process dies silently on any
+// unhandled promise rejection. With them it logs and keeps serving tools.
+process.on('unhandledRejection', err => {
+  process.stderr.write(`discord channel: unhandled rejection: ${err}\n`)
+})
+process.on('uncaughtException', err => {
+  process.stderr.write(`discord channel: uncaught exception: ${err}\n`)
+})
+
 const client = new Client({
   intents: [
     GatewayIntentBits.DirectMessages,
@@ -342,7 +351,7 @@ function checkApprovals(): void {
   }
 }
 
-if (!STATIC) setInterval(checkApprovals, 5000)
+if (!STATIC) setInterval(checkApprovals, 5000).unref()
 
 // Discord caps messages at 2000 chars (hard limit — larger sends reject).
 // Split long replies, preferring paragraph boundaries when chunkMode is
@@ -637,6 +646,25 @@ mcp.setRequestHandler(CallToolRequestSchema, async req => {
 
 await mcp.connect(new StdioServerTransport())
 
+// When Claude Code closes the MCP connection, stdin gets EOF. Without this
+// the gateway stays connected as a zombie holding resources.
+let shuttingDown = false
+function shutdown(): void {
+  if (shuttingDown) return
+  shuttingDown = true
+  process.stderr.write('discord channel: shutting down\n')
+  setTimeout(() => process.exit(0), 2000)
+  void Promise.resolve(client.destroy()).finally(() => process.exit(0))
+}
+process.stdin.on('end', shutdown)
+process.stdin.on('close', shutdown)
+process.on('SIGTERM', shutdown)
+process.on('SIGINT', shutdown)
+
+client.on('error', err => {
+  process.stderr.write(`discord channel: client error: ${err}\n`)
+})
+
 client.on('messageCreate', msg => {
   if (msg.author.bot) return
   handleInbound(msg).catch(e => process.stderr.write(`discord: handleInbound failed: ${e}\n`))
@@ -685,7 +713,7 @@ async function handleInbound(msg: Message): Promise<void> {
   // forgeable by any allowlisted sender typing that string.
   const content = msg.content || (atts.length > 0 ? '(attachment)' : '')
 
-  void mcp.notification({
+  mcp.notification({
     method: 'notifications/claude/channel',
     params: {
       content,
@@ -698,6 +726,8 @@ async function handleInbound(msg: Message): Promise<void> {
         ...(atts.length > 0 ? { attachment_count: String(atts.length), attachments: atts.join('; ') } : {}),
       },
     },
+  }).catch(err => {
+    process.stderr.write(`discord channel: failed to deliver inbound to Claude: ${err}\n`)
   })
 }
 
@@ -705,4 +735,7 @@ client.once('ready', c => {
   process.stderr.write(`discord channel: gateway connected as ${c.user.tag}\n`)
 })
 
-await client.login(TOKEN)
+client.login(TOKEN).catch(err => {
+  process.stderr.write(`discord channel: login failed: ${err}\n`)
+  process.exit(1)
+})