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

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 { join } from "node:path";
import { tmpdir } from "node:os";
@@ -171,70 +171,88 @@ export class AgentRuntime {
sessionId?: string,
): Promise<ClaudeJsonResponse> {
return new Promise((resolve, reject) => {
// Build args — keep it minimal and match what works on the CLI directly
const args: string[] = [
"-p", promptText,
"--output-format", "json",
"--system-prompt-file", systemPromptFile,
"--dangerously-skip-permissions",
"--append-system-prompt-file", systemPromptFile,
"--verbose",
];
if (sessionId) {
args.push("--resume", sessionId);
}
if (this.config.allowedTools.length > 0) {
args.push("--allowedTools", ...this.config.allowedTools);
// --allowedTools expects each tool as a separate quoted arg
for (const tool of this.config.allowedTools) {
args.push("--allowedTools", tool);
}
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(
this.config.claudeCliPath,
args,
{
timeout: this.config.queryTimeoutMs,
maxBuffer: 10 * 1024 * 1024,
encoding: "utf-8",
},
(error, stdout, stderr) => {
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") {
reject(new Error("Query timed out"));
return;
}
reject(new Error(`Claude CLI error: ${stderr || error.message}`));
const child = spawn(this.config.claudeCliPath, args, {
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (data: Buffer) => {
stdout += data.toString();
});
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"));
}, this.config.queryTimeoutMs);
child.on("close", (code) => {
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)}`);
}
if (code !== 0 && code !== null) {
reject(new Error(`Claude CLI error (exit ${code}): ${stderr.slice(0, 500) || "unknown error"}`));
return;
}
try {
const lines = stdout.trim().split("\n");
const lastLine = lines[lines.length - 1];
const parsed = JSON.parse(lastLine) as ClaudeJsonResponse;
if (parsed.is_error) {
reject(new Error(`Claude error: ${parsed.result ?? "Unknown error"}`));
return;
}
console.log(`[DEBUG] Claude CLI stdout (${stdout.length} chars): ${stdout.slice(0, 300)}...`);
if (stderr) {
console.log(`[DEBUG] Claude CLI stderr: ${stderr.slice(0, 300)}`);
}
try {
const lines = stdout.trim().split("\n");
const lastLine = lines[lines.length - 1];
const parsed = JSON.parse(lastLine) as ClaudeJsonResponse;
if (parsed.is_error) {
reject(new Error(`Claude error: ${parsed.result ?? "Unknown error"}`));
return;
}
resolve(parsed);
} catch (parseError) {
resolve({
type: "result",
result: stdout.trim(),
});
}
},
);
resolve(parsed);
} catch {
resolve({
type: "result",
result: stdout.trim(),
});
}
});
child.on("error", (err) => {
clearTimeout(timer);
console.error(`[DEBUG] Failed to spawn Claude CLI: ${err.message}`);
reject(new Error(`Failed to spawn Claude CLI: ${err.message}`));
});