Initial commit: Discord-Claude Gateway with event-driven agent runtime

This commit is contained in:
2026-02-22 01:07:02 -05:00
parent ffcbcd874a
commit 8bb11b19d8
3 changed files with 68 additions and 46 deletions

1
references/nanoclaw Submodule

Submodule references/nanoclaw added at 1980d97d90

View File

@@ -1,4 +1,4 @@
import { execFile } from "node:child_process"; import { spawn } from "node:child_process";
import { writeFile, unlink } from "node:fs/promises"; import { writeFile, unlink } from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
@@ -171,47 +171,65 @@ export class AgentRuntime {
sessionId?: string, sessionId?: string,
): Promise<ClaudeJsonResponse> { ): Promise<ClaudeJsonResponse> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Build args — keep it minimal and match what works on the CLI directly
const args: string[] = [ const args: string[] = [
"-p", promptText, "-p", promptText,
"--output-format", "json", "--output-format", "json",
"--system-prompt-file", systemPromptFile,
"--dangerously-skip-permissions", "--dangerously-skip-permissions",
"--append-system-prompt-file", systemPromptFile,
"--verbose",
]; ];
if (sessionId) { if (sessionId) {
args.push("--resume", sessionId); args.push("--resume", sessionId);
} }
if (this.config.allowedTools.length > 0) { // --allowedTools expects each tool as a separate quoted arg
args.push("--allowedTools", ...this.config.allowedTools); for (const tool of this.config.allowedTools) {
args.push("--allowedTools", tool);
} }
args.push("--max-turns", "25"); args.push("--max-turns", "25");
console.log(`[DEBUG] Spawning Claude CLI: ${this.config.claudeCliPath} -p "${promptText}" --output-format json --system-prompt-file ${systemPromptFile} ... (${args.length} args total)`); console.log(`[DEBUG] Spawning: ${this.config.claudeCliPath} args=${JSON.stringify(args.slice(0, 8))}... (${args.length} total)`);
const child = execFile( const child = spawn(this.config.claudeCliPath, args, {
this.config.claudeCliPath, stdio: ["ignore", "pipe", "pipe"],
args, });
{
timeout: this.config.queryTimeoutMs, let stdout = "";
maxBuffer: 10 * 1024 * 1024, let stderr = "";
encoding: "utf-8",
}, child.stdout.on("data", (data: Buffer) => {
(error, stdout, stderr) => { stdout += data.toString();
if (error) { });
console.error(`[DEBUG] Claude CLI error: code=${error.code}, killed=${error.killed}, stderr=${stderr?.slice(0, 500)}`);
if (error.killed || error.code === "ETIMEDOUT") { child.stderr.on("data", (data: Buffer) => {
const chunk = data.toString();
stderr += chunk;
// Log stderr in real-time so we can see what's happening
if (chunk.trim()) {
console.log(`[DEBUG] Claude stderr: ${chunk.trim().slice(0, 200)}`);
}
});
const timer = setTimeout(() => {
console.log(`[DEBUG] Timeout reached, killing Claude CLI process`);
child.kill("SIGTERM");
reject(new Error("Query timed out")); reject(new Error("Query timed out"));
return; }, this.config.queryTimeoutMs);
}
reject(new Error(`Claude CLI error: ${stderr || error.message}`)); child.on("close", (code) => {
return; clearTimeout(timer);
console.log(`[DEBUG] Claude CLI exited: code=${code}, stdout=${stdout.length} chars`);
if (stdout.length > 0) {
console.log(`[DEBUG] Claude stdout preview: ${stdout.slice(0, 300)}`);
} }
console.log(`[DEBUG] Claude CLI stdout (${stdout.length} chars): ${stdout.slice(0, 300)}...`); if (code !== 0 && code !== null) {
if (stderr) { reject(new Error(`Claude CLI error (exit ${code}): ${stderr.slice(0, 500) || "unknown error"}`));
console.log(`[DEBUG] Claude CLI stderr: ${stderr.slice(0, 300)}`); return;
} }
try { try {
@@ -225,16 +243,16 @@ export class AgentRuntime {
} }
resolve(parsed); resolve(parsed);
} catch (parseError) { } catch {
resolve({ resolve({
type: "result", type: "result",
result: stdout.trim(), result: stdout.trim(),
}); });
} }
}, });
);
child.on("error", (err) => { child.on("error", (err) => {
clearTimeout(timer);
console.error(`[DEBUG] Failed to spawn Claude CLI: ${err.message}`); console.error(`[DEBUG] Failed to spawn Claude CLI: ${err.message}`);
reject(new Error(`Failed to spawn Claude CLI: ${err.message}`)); reject(new Error(`Failed to spawn Claude CLI: ${err.message}`));
}); });

View File

@@ -21,7 +21,10 @@ export function shouldIgnoreMessage(message: { author: { bot: boolean } }): bool
} }
export function extractPromptFromMention(content: string, botId: string): string { export function extractPromptFromMention(content: string, botId: string): string {
return content.replace(new RegExp(`<@!?${botId}>`, "g"), "").trim(); // Remove user mentions (<@ID> or <@!ID>) and role mentions (<@&ID>) for the bot
return content
.replace(/<@[!&]?\d+>/g, "")
.trim();
} }
export class DiscordBot { export class DiscordBot {