"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.runAgent = runAgent; const child_process_1 = require("child_process"); const path = __importStar(require("path")); const identity_1 = require("./identity"); const loader_1 = require("../config/loader"); /** * Resolves the Claude CLI command and timeout from configuration. * Priority: CLAUDE_PATH env var > disclaw.yaml claude_command > "claude" default. */ function resolveClaudeSettings() { let command = "claude"; let timeoutMs = 120_000; try { const rootDir = path.resolve(__dirname, "../.."); const config = (0, loader_1.loadDisclawConfig)(rootDir); if (config.claude_command) { command = config.claude_command; } if (config.claude_timeout_seconds) { timeoutMs = config.claude_timeout_seconds * 1000; } } catch { // Config not available, use defaults } // CLAUDE_PATH env var takes highest priority for the command if (process.env.CLAUDE_PATH) { command = process.env.CLAUDE_PATH; } return { command, timeoutMs }; } /** * Builds the full prompt string including conversation history context. * The agent identity is handled by CLAUDE.md in the workspace directory, * which Claude Code reads automatically. */ function buildPrompt(userMessage, conversationHistory, channelName) { const parts = []; // Add conversation history if present (oldest first) const history = [...conversationHistory].reverse(); if (history.length > 0) { parts.push("Previous conversation context:"); parts.push(""); for (const msg of history) { const author = msg.author_name ?? msg.role; parts.push(`[${msg.role}] ${author}: ${msg.content}`); parts.push(""); } parts.push("---"); parts.push(""); } // Add runtime context parts.push(`Discord channel: #${channelName}`); parts.push(`Date: ${new Date().toISOString().split("T")[0]}`); parts.push(""); // Add the actual user message parts.push(`New message:`); parts.push(userMessage); return parts.join("\n"); } /** * Parses the JSON output from `claude -p --output-format json`. * Extracts the text content from the response. * * The JSON output format contains a "result" field with the response text, * or an array of content blocks with type "text". */ function parseClaudeJsonOutput(raw) { const trimmed = raw.trim(); if (!trimmed) { return "(no response from agent)"; } try { const parsed = JSON.parse(trimmed); // Format 1: { result: "text" } if (typeof parsed.result === "string") { return parsed.result; } // Format 2: { content: [{ type: "text", text: "..." }] } or similar if (Array.isArray(parsed.content)) { const texts = []; for (const block of parsed.content) { if (block && block.type === "text" && typeof block.text === "string") { texts.push(block.text); } } if (texts.length > 0) { return texts.join("\n"); } } // Format 3: Direct array of content blocks if (Array.isArray(parsed)) { const texts = []; for (const block of parsed) { if (block && block.type === "text" && typeof block.text === "string") { texts.push(block.text); } } if (texts.length > 0) { return texts.join("\n"); } } // Fallback: stringify the whole response return typeof parsed === "string" ? parsed : JSON.stringify(parsed, null, 2); } catch { // Not valid JSON -- return the raw output as plain text return trimmed; } } /** * Runs the Claude Code CLI as a child process and returns the text response. * * The CLI is invoked with: * claude -p "prompt" --output-format json * * The working directory is set to the workspace path so Claude Code * automatically reads the CLAUDE.md file for agent identity and context. * * Timeout defaults to 120 seconds. The process is killed if it exceeds * the timeout. */ async function runAgent(options) { const { workspacePath, channelName, userMessage, conversationHistory, abortController, } = options; // Verify the workspace has an agent.yaml (validates it exists) (0, identity_1.loadAgentIdentity)(workspacePath); const settings = resolveClaudeSettings(); const prompt = buildPrompt(userMessage, conversationHistory, channelName); const timeoutMs = settings.timeoutMs; return new Promise((resolve, reject) => { const args = [ "-p", prompt, "--output-format", "json", ]; const child = (0, child_process_1.spawn)(settings.command, args, { cwd: workspacePath, env: { ...process.env, // Ensure Claude Code does not prompt for interactive input CI: "true", }, stdio: ["pipe", "pipe", "pipe"], shell: true, }); let stdout = ""; let stderr = ""; let killed = false; // Collect stdout child.stdout.on("data", (data) => { stdout += data.toString("utf-8"); }); // Collect stderr child.stderr.on("data", (data) => { stderr += data.toString("utf-8"); }); // Timeout handling const timer = setTimeout(() => { killed = true; child.kill("SIGTERM"); // Give it a moment to die gracefully, then force kill setTimeout(() => { try { child.kill("SIGKILL"); } catch { // Already dead } }, 5000); }, timeoutMs); // Abort controller support if (abortController) { abortController.signal.addEventListener("abort", () => { killed = true; child.kill("SIGTERM"); }); } child.on("error", (err) => { clearTimeout(timer); if (err.message.includes("ENOENT")) { reject(new Error(`Claude Code CLI not found at "${settings.command}". ` + `Install it with: npm install -g @anthropic-ai/claude-code\n` + `Or set CLAUDE_PATH in your .env file.`)); } else { reject(new Error(`Failed to start Claude Code CLI: ${err.message}`)); } }); child.on("close", (code) => { clearTimeout(timer); if (killed) { reject(new Error(`Claude Code CLI timed out after ${timeoutMs / 1000} seconds.`)); return; } if (code !== 0) { const errorDetail = stderr.trim() || stdout.trim() || `exit code ${code}`; reject(new Error(`Claude Code CLI error: ${errorDetail}`)); return; } const response = parseClaudeJsonOutput(stdout); resolve(response); }); // Close stdin immediately since we pass everything via args child.stdin.end(); }); } //# sourceMappingURL=runner.js.map