Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions src/dm-result.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,32 @@
If voice is connected, does nothing (voice agent will speak the result).
If voice is disconnected, sends the result to the owner's Discord DM.

Requires DISCORD_TOKEN in .env and the Discord bridge running.
Requires DISCORD_BOT_TOKEN in ~/.claude/channels/discord/.env.
"""

import json
import os
import subprocess
import sys
import urllib.request
from pathlib import Path

REPO = Path(__file__).resolve().parent.parent
DM_CHANNEL = "1485370959870431433" # Owner's Discord DM channel
SSE_STATUS_URL = "http://localhost:8080/sse-status"

# Read DM channel from discord .env or repo .env (not hardcoded)
DM_CHANNEL = ""
for env_path in [
Path.home() / ".claude" / "channels" / "discord" / ".env",
REPO / ".env",
]:
if env_path.exists():
for line in env_path.read_text().splitlines():
if line.startswith("DISCORD_DM_CHANNEL="):
DM_CHANNEL = line.split("=", 1)[1].strip().strip('"').strip("'")
break
if DM_CHANNEL:
break


def voice_connected() -> bool:
"""Check if a voice client is currently connected."""
Expand All @@ -35,9 +47,10 @@ def voice_connected() -> bool:


def send_dm(text: str) -> bool:
"""Send text to owner's Discord DM via the MCP Discord bridge."""
# Use the discord bridge's reply tool via a task file approach,
# or call the Discord API directly if bot token is available.
"""Send text to owner's Discord DM via Discord API."""
if not DM_CHANNEL:
print("dm-result: DISCORD_DM_CHANNEL not set in discord .env or repo .env", file=sys.stderr)
return False
# Check both locations for the Discord bot token
token = ""
for env_path in [
Expand Down
29 changes: 28 additions & 1 deletion src/task-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

import { writeFileSync, readFileSync, existsSync, unlinkSync, mkdirSync, readdirSync, appendFileSync } from 'node:fs';
import { execSync } from 'node:child_process';
import { join } from 'node:path';
import { z } from 'zod';
import type { ToolDefinition } from 'bodhi-realtime-agent';
Expand Down Expand Up @@ -318,8 +319,34 @@ export function startResultWatcher(onResult: (result: string) => void, isClientC
const files = readdirSync(RESULT_DIR).filter(f => f.endsWith('.txt')).sort();
if (files.length === 0) return;

// Only deliver if a client is connected — otherwise keep files queued
// If no voice client connected, send results via Discord DM fallback
if (!isClientConnected()) {
for (const file of files) {
if (_deliveredResults.has(file)) continue;
const path = join(RESULT_DIR, file);
const result = readFileSync(path, 'utf-8').trim();
if (result) {
const taskId = file.replace('.txt', '');
console.log(`${ts()} [TaskBridge] Voice offline — sending ${file} via Discord DM`);
try {
execSync(`python3 "${join(REPO_DIR, 'src', 'dm-result.py')}" --file "${path}"`, { timeout: 15_000 });
} catch (e: any) {
console.error(`${ts()} [TaskBridge] DM fallback failed: ${e.message}`);
}
_sendTaskStatus?.(taskId, 'done', result.slice(0, 60), result);
_deliveredResults.add(file);
_pendingTasks.delete(taskId);
logConversation('core-agent', `[task:${taskId}] ${result.slice(0, 200)}`);
try {
fetch('http://localhost:7843/task-done', {
method: 'POST',
headers: _apiHeaders(),
body: JSON.stringify({ taskId, result }),
}).catch(() => {});
} catch {}
setTimeout(() => { try { unlinkSync(path); } catch {} }, 10_000);
}
}
return;
}

Expand Down
Loading