import Anthropic from "@anthropic-ai/sdk"; import { toolDefs, executeTool } from "./tools.js"; import * as store from "./store.js"; import * as tg from "./telegram.js"; const client = new Anthropic(); const MODEL = "claude-sonnet-4-20250514"; const MAX_TURNS = 50; // Map of agentId -> resolve function for when user replies const waitingForUser = new Map void>(); export function isWaitingForUser(agentId: number): boolean { return waitingForUser.has(agentId); } export function resolveUserResponse(agentId: number, response: string): void { const resolve = waitingForUser.get(agentId); if (resolve) { waitingForUser.delete(agentId); resolve(response); } } function buildSystemPrompt(task: string): string { return `You are an autonomous coding agent. You have been assigned a specific task. YOUR TASK: ${task} GUIDELINES: - Work independently to complete the task - Use the bash tool for running commands, git, etc. - Use read_file, write_file, edit_file for file operations - When you're done, call the "done" tool with a summary - If you need user input or a decision, call "ask_user" - Be efficient — don't explain what you're about to do, just do it - If something fails, try to fix it yourself before asking the user WORKING DIRECTORY: ${process.cwd()}`; } export async function runAgent(agentId: number): Promise { const agent = store.getAgent(agentId); if (!agent) return; store.updateAgent(agentId, { status: "working" }); store.addLog(agentId, "system", `Agent started: ${agent.task}`); const messages: Anthropic.MessageParam[] = [ { role: "user", content: agent.task }, ]; let turns = 0; try { while (turns < MAX_TURNS) { turns++; const response = await client.messages.create({ model: MODEL, max_tokens: 8096, system: buildSystemPrompt(agent.task), tools: toolDefs as any, messages, }); // Collect assistant content const assistantContent = response.content; messages.push({ role: "assistant", content: assistantContent }); // Log text blocks (no Telegram notification — reduces noise) for (const block of assistantContent) { if (block.type === "text" && block.text.trim()) { store.addLog(agentId, "assistant", block.text); } } // If no tool use, we're done if (response.stop_reason !== "tool_use") { store.updateAgent(agentId, { status: "done", summary: "Completed (no more actions)", }); store.addLog(agentId, "system", "Agent finished (end_turn)"); await tg.send( `✅ *Agent #${agentId}* finished.\nTask: ${agent.task}`, agent.chat_id, { reply_to: agent.thread_msg_id || undefined } ); return; } // Process tool calls const toolResults: Anthropic.ToolResultBlockParam[] = []; for (const block of assistantContent) { if (block.type !== "tool_use") continue; const toolName = block.name; const toolInput = block.input as Record; store.addLog( agentId, "tool", `${toolName}: ${JSON.stringify(toolInput).slice(0, 500)}` ); if (toolName === "ask_user") { // Pause and wait for user response store.updateAgent(agentId, { status: "waiting" }); await tg.send( `❓ *Agent #${agentId}* needs your input:\n\n${toolInput.question}`, agent.chat_id, { reply_to: agent.thread_msg_id || undefined, keyboard: [ [{ text: "💬 Reply", callback_data: `talk_${agentId}` }], ], } ); store.addLog(agentId, "system", `Waiting for user: ${toolInput.question}`); // Wait for user response const userResponse = await new Promise((resolve) => { waitingForUser.set(agentId, resolve); }); store.updateAgent(agentId, { status: "working" }); store.addLog(agentId, "user", `User replied: ${userResponse}`); toolResults.push({ type: "tool_result", tool_use_id: block.id, content: `User responded: ${userResponse}`, }); continue; } if (toolName === "done") { const summary = (toolInput.summary as string) || "Task completed"; store.updateAgent(agentId, { status: "done", summary }); store.addLog(agentId, "system", `Done: ${summary}`); await tg.send( `✅ *Agent #${agentId}* completed!\n\n*Summary:* ${summary}\n*Task:* ${agent.task}`, agent.chat_id, { reply_to: agent.thread_msg_id || undefined } ); toolResults.push({ type: "tool_result", tool_use_id: block.id, content: summary, }); // Push tool results and stop messages.push({ role: "user", content: toolResults }); return; } // Execute the tool const { result } = executeTool(toolName, toolInput); store.addLog( agentId, "tool_result", `${toolName} → ${result.slice(0, 500)}` ); toolResults.push({ type: "tool_result", tool_use_id: block.id, content: result, }); } messages.push({ role: "user", content: toolResults }); } // Hit max turns store.updateAgent(agentId, { status: "done", summary: `Stopped after ${MAX_TURNS} turns`, }); await tg.send( `⚠️ *Agent #${agentId}* hit max turns (${MAX_TURNS}). Task: ${agent.task}`, agent.chat_id, { reply_to: agent.thread_msg_id || undefined } ); } catch (e: any) { console.error(`[agent ${agentId}] error:`, e); store.updateAgent(agentId, { status: "error", error: e.message }); store.addLog(agentId, "error", e.message); await tg.send( `❌ *Agent #${agentId}* error:\n${e.message?.slice(0, 500)}`, agent.chat_id, { reply_to: agent.thread_msg_id || undefined } ); } }