diff --git a/extensions/easymode.ts b/extensions/easymode.ts new file mode 100644 index 0000000..188b3e3 --- /dev/null +++ b/extensions/easymode.ts @@ -0,0 +1,641 @@ +/** + * EasyMode โ€” All-in-one beginner-friendly Pi extension + * + * Built-in features: + * ๐ŸŽฏ Goal Keeper โ€” set a focus so the AI stays on track + * ๐Ÿ›ก๏ธ Safety Guard โ€” catches dangerous commands before they run + * ๐Ÿ“Š Smart Footer โ€” model, context bar, cost, git branch, tool tally + * ๐Ÿค– Agent Presets โ€” switch AI personality with /agent + * ๐ŸŸข /menu โ€” visual command picker (no memorization needed) + * ๐Ÿ›‘ /stop โ€” abort the current AI action + * ๐Ÿ’ก /explain โ€” re-explain the last action in plain English + * ๐Ÿ”– /bookmark โ€” save + list important moments + * โ†ฉ๏ธ /undo โ€” how to revert the last file change + * ๐Ÿ“‹ /status โ€” full session dashboard + * ๐Ÿ“– /help โ€” quick reference + * ๐Ÿ”„ /quickstart โ€” re-run the welcome wizard + * + * Usage: pi -e extensions/easymode.ts + * + * Or with theme cycling: + * pi -e extensions/easymode.ts -e extensions/theme-cycler.ts + */ + +import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; +import { isToolCallEventType } from "@mariozechner/pi-coding-agent"; +import type { AssistantMessage } from "@mariozechner/pi-ai"; +import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; +import { basename } from "node:path"; +import { applyExtensionDefaults } from "./themeMap.ts"; + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// Agent Presets +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +interface AgentPreset { + name: string; + emoji: string; + description: string; + systemPrompt: string; +} + +const AGENT_PRESETS: AgentPreset[] = [ + { + name: "Default", + emoji: "๐Ÿค–", + description: "Standard coding assistant โ€” no restrictions", + systemPrompt: "", + }, + { + name: "Explain Like I'm 5", + emoji: "๐Ÿ‘ถ", + description: "Explains everything in the simplest terms possible", + systemPrompt: `You are a patient teacher. The user is a beginner. +- Explain EVERY concept in simple terms before using it +- Use analogies and real-world comparisons +- Show the "before" and "after" of every change +- Never assume the user knows technical jargon โ€” define it first +- After each code change, add a brief "What just happened?" summary +- If a command could break something, warn the user FIRST`, + }, + { + name: "Guided Builder", + emoji: "๐Ÿ—๏ธ", + description: "Step-by-step building with confirmations at each stage", + systemPrompt: `You are a guided builder. Help the user build things step-by-step. +- Break every task into numbered steps and show the plan first +- Ask for confirmation before each major step +- After each change, briefly explain what you did and why +- If there are multiple approaches, list 2-3 options with pros/cons +- Always suggest the safest option as the default +- Show file paths and explain the project structure as you go`, + }, + { + name: "Code Reviewer", + emoji: "๐Ÿ”", + description: "Reviews code and suggests improvements without changing anything", + systemPrompt: `You are a code reviewer. Review, don't modify. +- DO NOT write or edit any files unless explicitly asked +- Point out bugs, security issues, and improvement opportunities +- Rate code quality on a 1-5 scale with specific reasons +- Suggest improvements as clear before/after examples +- Focus on: readability, security, performance, maintainability +- Be encouraging โ€” highlight what's done well too`, + }, + { + name: "Debug Detective", + emoji: "๐Ÿ•ต๏ธ", + description: "Focused on finding and fixing bugs", + systemPrompt: `You are a debug detective. Help find and fix bugs systematically. +- Always ask to see error messages and logs first +- Explain your debugging thought process step by step +- List possible causes ranked by likelihood +- Test hypotheses one at a time +- After fixing, explain what caused the bug and how to prevent it +- Suggest adding error handling or tests to prevent recurrence`, + }, + { + name: "Safe Mode", + emoji: "๐Ÿ”’", + description: "Extra cautious โ€” confirms every file modification", + systemPrompt: `You are in Safe Mode. Maximum caution. +- ALWAYS show the exact changes you plan to make BEFORE making them +- Ask for explicit "yes" confirmation before ANY file write, edit, or bash command +- Never run destructive commands (rm, drop, truncate, etc.) without double-confirming +- Create backups before modifying existing files when possible +- After each change, verify the change was correct by reading the file back +- If something goes wrong, immediately suggest how to undo it`, + }, +]; + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// Dangerous Command Patterns +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +const DANGEROUS_PATTERNS: { pattern: RegExp; reason: string }[] = [ + { pattern: /rm\s+(-[rRf]+\s+|.*\s+-[rRf]+)/, reason: "Recursive/forced delete detected" }, + { pattern: /rm\s+-rf\s+[\/~]/, reason: "Deleting from root or home directory!" }, + { pattern: /DROP\s+(TABLE|DATABASE|SCHEMA)/i, reason: "SQL DROP command detected" }, + { pattern: /TRUNCATE\s+TABLE/i, reason: "SQL TRUNCATE command detected" }, + { pattern: /DELETE\s+FROM\s+\w+\s*(;|$)/i, reason: "DELETE without WHERE clause" }, + { pattern: />\s*\/dev\/sd[a-z]/, reason: "Writing directly to disk device" }, + { pattern: /mkfs\./, reason: "Filesystem format command detected" }, + { pattern: /dd\s+if=/, reason: "dd command โ€” raw disk copy" }, + { pattern: /chmod\s+-R\s+777/, reason: "Recursive world-writable permissions" }, + { pattern: /:(){ :\|:& };:/, reason: "Fork bomb detected!" }, + { pattern: /curl.*\|\s*(ba)?sh/, reason: "Piping remote script to shell" }, + { pattern: /wget.*\|\s*(ba)?sh/, reason: "Piping remote script to shell" }, + { pattern: /git\s+push\s+.*--force/, reason: "Force push โ€” may overwrite remote history" }, + { pattern: /git\s+reset\s+--hard/, reason: "Hard reset โ€” may lose uncommitted changes" }, + { pattern: /npm\s+publish/, reason: "Publishing to npm registry" }, + { pattern: /docker\s+system\s+prune/, reason: "Docker system prune โ€” removes all unused data" }, +]; + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// Extension +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +export default function (pi: ExtensionAPI) { + let purpose: string | undefined; + let activePreset: AgentPreset = AGENT_PRESETS[1]; // Default to "Explain Like I'm 5" for beginners + let activeCtx: ExtensionContext | undefined; + const toolCounts: Record = {}; + const bookmarks: { timestamp: string; note: string }[] = []; + let lastFileChange: { tool: string; path: string } | null = null; + let toolRunsSinceLastTip = 0; + + // โ”€โ”€ Tool tracking + contextual tips โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + pi.on("tool_execution_end", async (event) => { + toolCounts[event.toolName] = (toolCounts[event.toolName] || 0) + 1; + + // Show a helpful hint every 5 tool runs + toolRunsSinceLastTip++; + if (activeCtx && toolRunsSinceLastTip >= 5) { + toolRunsSinceLastTip = 0; + const tips = [ + "๐Ÿ’ก Confused? Type /explain and the AI will break down what just happened.", + "๐Ÿ’ก Want to save your progress? Type /bookmark to mark this moment.", + "๐Ÿ’ก Type /menu to see everything you can do.", + "๐Ÿ’ก Things going wrong? Type /stop to pause the AI immediately.", + "๐Ÿ’ก Type /undo to see how to reverse the last file change.", + "๐Ÿ’ก Type /status to see a summary of your whole session.", + ]; + const tip = tips[Math.floor(Math.random() * tips.length)]; + activeCtx.ui.notify(tip, "info"); + } + }); + + // โ”€โ”€ Track file changes for /undo โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + pi.on("tool_call", async (event, _ctx) => { + if (isToolCallEventType("write", event) || isToolCallEventType("edit", event)) { + lastFileChange = { tool: event.toolName, path: event.input.path }; + } + return { block: false }; + }); + + // โ”€โ”€ Welcome Wizard โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + async function runWelcomeWizard(ctx: ExtensionContext) { + // Step 1: Welcome + ctx.ui.notify( + "๐Ÿ‘‹ Welcome to EasyMode!\n\n" + + "This is your AI coding assistant. You type what you want\n" + + "in plain English, and it writes the code for you.\n\n" + + "Let's get you set up in 30 seconds...", + "info" + ); + + // Step 2: Pick a personality + const personalityOptions = AGENT_PRESETS.map((p) => `${p.emoji} ${p.name} โ€” ${p.description}`); + const personalityChoice = await ctx.ui.select( + "Step 1 of 2: How should the AI talk to you?", + personalityOptions + ); + + if (personalityChoice !== undefined) { + const idx = personalityOptions.indexOf(personalityChoice); + activePreset = AGENT_PRESETS[idx]; + ctx.ui.setStatus("easymode", `${activePreset.emoji} ${activePreset.name}`); + ctx.ui.notify(`Great choice! ${activePreset.emoji} ${activePreset.name} activated.`, "success"); + } else { + ctx.ui.notify(`Using default: ${activePreset.emoji} ${activePreset.name}`, "info"); + } + + // Step 3: Set a goal + const answer = await ctx.ui.input( + "Step 2 of 2: What do you want to do today?", + "Examples: 'Make a website', 'Fix the bug on line 42', 'Explain this code to me'" + ); + + if (answer && answer.trim()) { + purpose = answer.trim(); + ctx.ui.notify(`๐ŸŽฏ Goal set: ${purpose}`, "success"); + setPurposeWidget(ctx); + } else { + ctx.ui.notify("No problem! You can set a goal anytime by typing /goal", "info"); + } + + // Step 4: Quick orientation + ctx.ui.notify( + "โœ… You're all set! Here's what you need to know:\n\n" + + " ๐Ÿ“ Just type what you want in plain English\n" + + " ๐ŸŸข /menu โ€” see all available commands\n" + + " ๐Ÿ›‘ /stop โ€” stop the AI if it's doing something wrong\n" + + " โ“ /explain โ€” ask the AI to explain what it just did\n\n" + + "โš ๏ธ The AI will ask for your OK before doing anything dangerous.\n\n" + + "Go ahead โ€” type your first message! ๐Ÿš€", + "success" + ); + } + + // โ”€โ”€ Session start โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + pi.on("session_start", async (_event, ctx) => { + applyExtensionDefaults(import.meta.url, ctx); + activeCtx = ctx; + + // Fire-and-forget โ€” don't block TUI startup + void runWelcomeWizard(ctx); + + // Footer + ctx.ui.setFooter((tui, theme, footerData) => { + const unsub = footerData.onBranchChange(() => tui.requestRender()); + + return { + dispose: unsub, + invalidate() {}, + render(width: number): string[] { + // Accumulate tokens + cost + let tokIn = 0, tokOut = 0, cost = 0; + for (const entry of ctx.sessionManager.getBranch()) { + if (entry.type === "message" && entry.message.role === "assistant") { + const m = entry.message as AssistantMessage; + tokIn += m.usage.input; + tokOut += m.usage.output; + cost += m.usage.cost.total; + } + } + + const fmt = (n: number) => n < 1000 ? `${n}` : `${(n / 1000).toFixed(1)}k`; + const model = ctx.model?.id || "no-model"; + const dir = basename(ctx.cwd); + const branch = footerData.getGitBranch(); + + // Context bar + const usage = ctx.getContextUsage(); + const pct = usage?.percent ?? 0; + const filled = Math.round(pct / 10); + const bar = "โ–ˆ".repeat(filled) + "โ–‘".repeat(10 - filled); + + // Line 1: model + context + agent + cost + const l1Left = + theme.fg("dim", ` ${model} `) + + (pct < 70 ? theme.fg("success", bar) : pct < 90 ? theme.fg("warning", bar) : theme.fg("error", bar)) + + theme.fg("dim", ` ${Math.round(pct)}% `) + + theme.fg("accent", `${activePreset.emoji} ${activePreset.name}`); + + const l1Right = + theme.fg("success", fmt(tokIn)) + + theme.fg("dim", "โ†“ ") + + theme.fg("accent", fmt(tokOut)) + + theme.fg("dim", "โ†‘ ") + + theme.fg("warning", `$${cost.toFixed(4)}`) + + theme.fg("dim", " "); + + const pad1 = " ".repeat(Math.max(1, width - visibleWidth(l1Left) - visibleWidth(l1Right))); + const line1 = truncateToWidth(l1Left + pad1 + l1Right, width, ""); + + // Line 2: cwd + branch + tools + const l2Left = + theme.fg("dim", ` ${dir}`) + + (branch + ? theme.fg("dim", " ") + theme.fg("warning", "(") + theme.fg("success", branch) + theme.fg("warning", ")") + : ""); + + const entries = Object.entries(toolCounts); + const l2Right = entries.length === 0 + ? theme.fg("dim", "ready ") + : entries + .map(([name, count]) => theme.fg("accent", name) + theme.fg("dim", ":") + theme.fg("success", `${count}`)) + .join(theme.fg("dim", " ")) + " "; + + const pad2 = " ".repeat(Math.max(1, width - visibleWidth(l2Left) - visibleWidth(l2Right))); + const line2 = truncateToWidth(l2Left + pad2 + l2Right, width, ""); + + return [line1, line2]; + }, + }; + }); + + ctx.ui.setStatus("easymode", `${activePreset.emoji} ${activePreset.name}`); + }); + + pi.on("session_switch", async (_event, ctx) => { activeCtx = ctx; }); + + // โ”€โ”€ Safety guard โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + pi.on("tool_call", async (event, ctx) => { + if (isToolCallEventType("bash", event)) { + const cmd = event.input.command; + for (const { pattern, reason } of DANGEROUS_PATTERNS) { + if (pattern.test(cmd)) { + const confirmed = await ctx.ui.confirm( + "โš ๏ธ Safety Warning", + `${reason}\n\nCommand: ${cmd}\n\nAre you sure you want to run this?`, + { timeout: 30000 } + ); + if (!confirmed) { + ctx.ui.notify(`๐Ÿ›ก๏ธ Blocked: ${reason}`, "warning"); + ctx.abort(); + return { + block: true, + reason: `๐Ÿ›‘ BLOCKED by Safety Guard: ${reason}\n\nThe user chose not to run this command. Ask them what they'd like to do instead.`, + }; + } + break; + } + } + } + return { block: false }; + }); + + // โ”€โ”€ System prompt injection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + pi.on("before_agent_start", async (event) => { + let extra = ""; + if (activePreset.systemPrompt) { + extra += `\n\n\n${activePreset.systemPrompt}\n`; + } + if (purpose) { + extra += `\n\n\nThe user's goal for this session: ${purpose}\nKeep this goal in mind. If the conversation drifts, gently guide back.\n`; + } + if (!extra) return; + return { systemPrompt: event.systemPrompt + extra }; + }); + + // โ”€โ”€ Helper โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + function setPurposeWidget(ctx: ExtensionContext) { + ctx.ui.setWidget("purpose", () => ({ + render(width: number): string[] { + const label = " โœ… Working on: "; + const content = purpose!; + const line = truncateToWidth(label + content, width - 2, "โ€ฆ"); + return [ + " ".repeat(width), + line + " ".repeat(Math.max(0, width - visibleWidth(line))), + " ".repeat(width), + ]; + }, + invalidate() {}, + })); + } + + // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + // Commands + // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + + pi.registerCommand("help", { + description: "Show all EasyMode commands", + handler: async (_args, ctx) => { + ctx.ui.notify( + "๐Ÿ“– EasyMode Commands:\n\n" + + " /menu โ€” ๐ŸŸข START HERE โ€” pick a command from a list\n" + + " /help โ€” You're looking at it!\n" + + " /agent โ€” Switch AI personality (beginner, builder, reviewer...)\n" + + " /goal โ€” Set or change your session goal\n" + + " /stop โ€” Immediately stop the current AI action\n" + + " /explain โ€” Ask the AI to explain what it just did\n" + + " /bookmark โ€” Save the current moment with a note\n" + + " /bookmarks โ€” See all saved bookmarks\n" + + " /undo โ€” Show how to undo the last file change\n" + + " /status โ€” Show session overview (goal, agent, stats)\n" + + " /quickstart โ€” Re-run the setup wizard\n" + + "\n๐Ÿ’ก Tips:\n" + + " โ€ข Just type what you want in plain English โ€” no special syntax needed\n" + + " โ€ข The bottom bar shows your context usage and cost\n" + + " โ€ข โš ๏ธ Dangerous commands always ask for your OK first", + "info" + ); + }, + }); + + pi.registerCommand("menu", { + description: "Pick a command from a visual menu", + handler: async (_args, ctx) => { + const menuItems = [ + "๐ŸŽฏ Set or change my goal", + "๐Ÿค– Switch AI personality", + "๐Ÿ’ก Explain what just happened", + "๐Ÿ”– Save a bookmark", + "๐Ÿ“‹ See all my bookmarks", + "โ†ฉ๏ธ Undo the last file change", + "๐Ÿ“Š Show session status", + "๐Ÿ›‘ Stop the AI", + "๐Ÿ”„ Re-run setup wizard", + "๐Ÿ“– Show all commands (help)", + ]; + const choice = await ctx.ui.select("What would you like to do?", menuItems); + if (choice === undefined) return; + + const idx = menuItems.indexOf(choice); + switch (idx) { + case 0: { // Goal + const answer = await ctx.ui.input("๐ŸŽฏ What's your goal?", purpose || "e.g. Fix the login bug..."); + if (answer && answer.trim()) { + purpose = answer.trim(); + ctx.ui.notify(`๐ŸŽฏ Goal updated: ${purpose}`, "success"); + setPurposeWidget(ctx); + } + return; + } + case 1: { // Agent + const options = AGENT_PRESETS.map((p) => `${p.emoji} ${p.name} โ€” ${p.description}`); + const agentChoice = await ctx.ui.select("Select AI Personality", options); + if (agentChoice !== undefined) { + const agentIdx = options.indexOf(agentChoice); + activePreset = AGENT_PRESETS[agentIdx]; + ctx.ui.setStatus("easymode", `${activePreset.emoji} ${activePreset.name}`); + ctx.ui.notify(`Switched to: ${activePreset.emoji} ${activePreset.name}\n${activePreset.description}`, "success"); + } + return; + } + case 2: // Explain + ctx.sendMessage("Please explain what you just did in simple terms that a beginner would understand. Use bullet points and avoid jargon. If you wrote code, explain what each part does."); + return; + case 3: { // Bookmark + const note = await ctx.ui.input("๐Ÿ”– Bookmark note", "e.g. Got login working, Before refactor..."); + if (note && note.trim()) { + const ts = new Date().toLocaleTimeString(); + bookmarks.push({ timestamp: ts, note: note.trim() }); + pi.appendEntry("easymode-bookmarks", { timestamp: ts, note: note.trim() }); + ctx.ui.notify(`๐Ÿ”– Saved: ${note.trim()} (${ts})`, "success"); + } + return; + } + case 4: { // Bookmarks + if (bookmarks.length === 0) return ctx.ui.notify("No bookmarks yet. Use /bookmark to save one.", "info"); + const list = bookmarks.map((b, i) => ` ${i + 1}. [${b.timestamp}] ${b.note}`).join("\n"); + return ctx.ui.notify(`๐Ÿ”– Bookmarks:\n\n${list}`, "info"); + } + case 5: { // Undo + if (!lastFileChange) { + return ctx.ui.notify("No file changes tracked yet.\n\n๐Ÿ’ก You can also tell the AI: \"Please undo the last change\"", "info"); + } + return ctx.ui.notify( + `โ†ฉ๏ธ Last change: ${lastFileChange.tool} โ†’ ${lastFileChange.path}\n\n` + + 'Tell the AI: "Please revert the last change"\n' + + `Or run: git checkout -- ${lastFileChange.path}`, + "info" + ); + } + case 6: { // Status + const usage = ctx.getContextUsage(); + const pct = usage?.percent ?? 0; + let tokIn = 0, tokOut = 0, cost = 0; + for (const entry of ctx.sessionManager.getBranch()) { + if (entry.type === "message" && entry.message.role === "assistant") { + const m = entry.message as AssistantMessage; + tokIn += m.usage.input; + tokOut += m.usage.output; + cost += m.usage.cost.total; + } + } + return ctx.ui.notify( + `๐Ÿ“‹ Session Status\n\n` + + ` ๐ŸŽฏ Goal: ${purpose || "(not set)"}\n` + + ` ๐Ÿค– Agent: ${activePreset.emoji} ${activePreset.name}\n` + + ` ๐Ÿ“Š Context: ${Math.round(pct)}% used\n` + + ` ๐Ÿ’ฐ Cost: $${cost.toFixed(4)}`, + "info" + ); + } + case 7: // Stop + ctx.abort(); + return ctx.ui.notify("๐Ÿ›‘ Stopped.", "warning"); + case 8: // Quickstart + await runWelcomeWizard(ctx); + return; + case 9: // Help + return ctx.ui.notify( + "๐Ÿ“– All commands start with / (slash):\n\n" + + " /menu /goal /agent /explain /bookmark\n" + + " /bookmarks /undo /status /stop /quickstart /help\n\n" + + "Or just type what you want in plain English!", + "info" + ); + } + }, + }); + + pi.registerCommand("quickstart", { + description: "Re-run the welcome wizard", + handler: async (_args, ctx) => { + await runWelcomeWizard(ctx); + }, + }); + + pi.registerCommand("agent", { + description: "Switch AI personality preset", + handler: async (_args, ctx) => { + const options = AGENT_PRESETS.map((p) => `${p.emoji} ${p.name} โ€” ${p.description}`); + const choice = await ctx.ui.select("Select AI Personality", options); + if (choice === undefined) return; + const idx = options.indexOf(choice); + activePreset = AGENT_PRESETS[idx]; + ctx.ui.setStatus("easymode", `${activePreset.emoji} ${activePreset.name}`); + ctx.ui.notify(`Switched to: ${activePreset.emoji} ${activePreset.name}\n${activePreset.description}`, "success"); + }, + }); + + pi.registerCommand("goal", { + description: "Set or change your session goal", + handler: async (_args, ctx) => { + const answer = await ctx.ui.input("๐ŸŽฏ What's your goal?", purpose || "e.g. Fix the login bug..."); + if (answer && answer.trim()) { + purpose = answer.trim(); + ctx.ui.notify(`๐ŸŽฏ Goal updated: ${purpose}`, "success"); + setPurposeWidget(ctx); + } + }, + }); + + pi.registerCommand("stop", { + description: "Immediately stop the current AI action", + handler: async (args, ctx) => { + const reason = (args || "").trim(); + ctx.abort(); + ctx.ui.notify(reason ? `๐Ÿ›‘ Stopped: ${reason}` : "๐Ÿ›‘ Stopped.", "warning"); + }, + }); + + pi.registerCommand("explain", { + description: "Ask the AI to explain its last action in simple terms", + handler: async (_args, ctx) => { + ctx.ui.notify("๐Ÿ’ก Asking AI to explain...", "info"); + ctx.sendMessage( + "Please explain what you just did in simple terms that a beginner would understand. " + + "Use bullet points and avoid jargon. If you wrote code, explain what each part does." + ); + }, + }); + + pi.registerCommand("bookmark", { + description: "Save a bookmark with a note", + handler: async (_args, ctx) => { + const note = await ctx.ui.input("๐Ÿ”– Bookmark note", "e.g. Got login working, Before refactor..."); + if (note && note.trim()) { + const ts = new Date().toLocaleTimeString(); + bookmarks.push({ timestamp: ts, note: note.trim() }); + pi.appendEntry("easymode-bookmarks", { timestamp: ts, note: note.trim() }); + ctx.ui.notify(`๐Ÿ”– Saved: ${note.trim()} (${ts})`, "success"); + } + }, + }); + + pi.registerCommand("bookmarks", { + description: "Show all saved bookmarks", + handler: async (_args, ctx) => { + if (bookmarks.length === 0) { + ctx.ui.notify("No bookmarks yet. Use /bookmark to save one.", "info"); + return; + } + const list = bookmarks.map((b, i) => ` ${i + 1}. [${b.timestamp}] ${b.note}`).join("\n"); + ctx.ui.notify(`๐Ÿ”– Bookmarks:\n\n${list}`, "info"); + }, + }); + + pi.registerCommand("undo", { + description: "Show how to undo the last file change", + handler: async (_args, ctx) => { + if (!lastFileChange) { + ctx.ui.notify( + "No file changes tracked yet.\n\n" + + "๐Ÿ’ก General undo:\n" + + " git checkout -- revert to last commit\n" + + " git diff see what changed\n" + + " git stash save & revert everything", + "info" + ); + return; + } + ctx.ui.notify( + `โ†ฉ๏ธ Last change: ${lastFileChange.tool} โ†’ ${lastFileChange.path}\n\n` + + "To undo:\n" + + ` git checkout -- ${lastFileChange.path}\n\n` + + "See diff:\n" + + ` git diff ${lastFileChange.path}\n\n` + + 'Or tell the AI: "Please revert the last change"', + "info" + ); + }, + }); + + pi.registerCommand("status", { + description: "Show session overview", + handler: async (_args, ctx) => { + const usage = ctx.getContextUsage(); + const pct = usage?.percent ?? 0; + let tokIn = 0, tokOut = 0, cost = 0; + for (const entry of ctx.sessionManager.getBranch()) { + if (entry.type === "message" && entry.message.role === "assistant") { + const m = entry.message as AssistantMessage; + tokIn += m.usage.input; + tokOut += m.usage.output; + cost += m.usage.cost.total; + } + } + const entries = Object.entries(toolCounts); + const toolSummary = entries.length === 0 + ? " None yet" + : entries.map(([n, c]) => ` ${n}: ${c}`).join("\n"); + + ctx.ui.notify( + `๐Ÿ“‹ Session Status\n\n` + + ` ๐ŸŽฏ Goal: ${purpose || "(not set โ€” use /goal)"}\n` + + ` ๐Ÿค– Agent: ${activePreset.emoji} ${activePreset.name}\n` + + ` ๐Ÿ“Š Context: ${Math.round(pct)}% used\n` + + ` ๐Ÿ’ฐ Cost: $${cost.toFixed(4)}\n` + + ` ๐Ÿ“จ Tokens: ${tokIn} in / ${tokOut} out\n` + + ` ๐Ÿ”– Bookmarks: ${bookmarks.length}\n` + + `\n๐Ÿ”ง Tools Used:\n${toolSummary}`, + "info" + ); + }, + }); +} diff --git a/extensions/themeMap.ts b/extensions/themeMap.ts index 19adcfb..e2f8331 100644 --- a/extensions/themeMap.ts +++ b/extensions/themeMap.ts @@ -38,6 +38,7 @@ export const THEME_MAP: Record = { "tilldone": "everforest", // task-focused calm "tool-counter": "synthwave", // techy metrics "tool-counter-widget":"synthwave", // same family + "easymode": "catppuccin-mocha", // soft, welcoming for beginners }; // โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ diff --git a/giveaway/pi-easymode b/giveaway/pi-easymode new file mode 160000 index 0000000..419f9a7 --- /dev/null +++ b/giveaway/pi-easymode @@ -0,0 +1 @@ +Subproject commit 419f9a7cfa50897973ae74f45315901bb7528726 diff --git a/justfile b/justfile index 743cf2a..1fe5fe2 100644 --- a/justfile +++ b/justfile @@ -83,6 +83,16 @@ ext-session-replay: ext-theme-cycler: pi -e extensions/theme-cycler.ts -e extensions/minimal.ts +# beginner + +# 19. EasyMode: all-in-one beginner-friendly agent with welcome wizard, /menu, safety guards, goals, agent presets +ext-easymode: + pi -e extensions/easymode.ts + +# 20. EasyMode + Theme Cycling +ext-easymode-themed: + pi -e extensions/easymode.ts -e extensions/theme-cycler.ts + # utils # Open pi with one or more stacked extensions in a new terminal: just open minimal tool-counter diff --git a/telegram-bot/bot.err b/telegram-bot/bot.err index e69de29..d5beb50 100644 --- a/telegram-bot/bot.err +++ b/telegram-bot/bot.err @@ -0,0 +1,1564 @@ +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 80 | let msg = `Network request for '${method}' failed!`; +81 | if (isTelegramError(err)) +82 | msg += ` (${err.status}: ${err.statusText})`; +83 | if (sensitiveLogs && err instanceof Error) +84 | msg += ` ${err.message}`; +85 | throw new HttpError(msg, err); + ^ +HttpError: Network request for 'getUpdates' failed! + error: DOMException { + stack: "", + code: 23, + name: "TimeoutError", + message: "The operation timed out.", + INDEX_SIZE_ERR: 1, + DOMSTRING_SIZE_ERR: 2, + HIERARCHY_REQUEST_ERR: 3, + WRONG_DOCUMENT_ERR: 4, + INVALID_CHARACTER_ERR: 5, + NO_DATA_ALLOWED_ERR: 6, + NO_MODIFICATION_ALLOWED_ERR: 7, + NOT_FOUND_ERR: 8, + NOT_SUPPORTED_ERR: 9, + INUSE_ATTRIBUTE_ERR: 10, + INVALID_STATE_ERR: 11, + SYNTAX_ERR: 12, + INVALID_MODIFICATION_ERR: 13, + NAMESPACE_ERR: 14, + INVALID_ACCESS_ERR: 15, + VALIDATION_ERR: 16, + TYPE_MISMATCH_ERR: 17, + SECURITY_ERR: 18, + NETWORK_ERR: 19, + ABORT_ERR: 20, + URL_MISMATCH_ERR: 21, + QUOTA_EXCEEDED_ERR: 22, + TIMEOUT_ERR: 23, + INVALID_NODE_TYPE_ERR: 24, + DATA_CLONE_ERR: 25, + toString: [Function: toString], +}, + + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/error.js:85:15) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +โŒ [775071081] Claude API timed out after 30s +โŒ [775071081] 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.0.content.0: unexpected `tool_use_id` found in `tool_result` blocks: toolu_0111xY3a7g8MwBw1Soxfkz8f. Each `tool_result` block must have a corresponding `tool_use` block in the previous message."},"request_id":"req_011CYiHQwfrCSQysQgGFK5B5"} +โŒ [775071081] Claude API timed out after 30s +โŒ [775071081] Claude summary timed out after 30s +โŒ [775071081] 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.0.content.0: unexpected `tool_use_id` found in `tool_result` blocks: toolu_01SBxLBHKov9GPKN6kP2wgcx. Each `tool_result` block must have a corresponding `tool_use` block in the previous message."},"request_id":"req_011CYiLavdTtRatZss7NfdLH"} +[grammY runner] Error while fetching updates: +[grammY runner] 80 | let msg = `Network request for '${method}' failed!`; +81 | if (isTelegramError(err)) +82 | msg += ` (${err.status}: ${err.statusText})`; +83 | if (sensitiveLogs && err instanceof Error) +84 | msg += ` ${err.message}`; +85 | throw new HttpError(msg, err); + ^ +HttpError: Network request for 'getUpdates' failed! + error: DOMException { + stack: "", + code: 23, + name: "TimeoutError", + message: "The operation timed out.", + INDEX_SIZE_ERR: 1, + DOMSTRING_SIZE_ERR: 2, + HIERARCHY_REQUEST_ERR: 3, + WRONG_DOCUMENT_ERR: 4, + INVALID_CHARACTER_ERR: 5, + NO_DATA_ALLOWED_ERR: 6, + NO_MODIFICATION_ALLOWED_ERR: 7, + NOT_FOUND_ERR: 8, + NOT_SUPPORTED_ERR: 9, + INUSE_ATTRIBUTE_ERR: 10, + INVALID_STATE_ERR: 11, + SYNTAX_ERR: 12, + INVALID_MODIFICATION_ERR: 13, + NAMESPACE_ERR: 14, + INVALID_ACCESS_ERR: 15, + VALIDATION_ERR: 16, + TYPE_MISMATCH_ERR: 17, + SECURITY_ERR: 18, + NETWORK_ERR: 19, + ABORT_ERR: 20, + URL_MISMATCH_ERR: 21, + QUOTA_EXCEEDED_ERR: 22, + TIMEOUT_ERR: 23, + INVALID_NODE_TYPE_ERR: 24, + DATA_CLONE_ERR: 25, + toString: [Function: toString], +}, + + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/error.js:85:15) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 80 | let msg = `Network request for '${method}' failed!`; +81 | if (isTelegramError(err)) +82 | msg += ` (${err.status}: ${err.statusText})`; +83 | if (sensitiveLogs && err instanceof Error) +84 | msg += ` ${err.message}`; +85 | throw new HttpError(msg, err); + ^ +HttpError: Network request for 'getUpdates' failed! + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/error.js:85:15) + +error: Unable to connect. Is the computer able to access the url? + path: "https://api.telegram.org/bot8409280209:AAGwJTR7qilJfLQkZcXkpaRJMp997LMnsEM/getUpdates", + errno: 0, + code: "ConnectionRefused" + + +[grammY runner] Error while fetching updates: +[grammY runner] 80 | let msg = `Network request for '${method}' failed!`; +81 | if (isTelegramError(err)) +82 | msg += ` (${err.status}: ${err.statusText})`; +83 | if (sensitiveLogs && err instanceof Error) +84 | msg += ` ${err.message}`; +85 | throw new HttpError(msg, err); + ^ +HttpError: Network request for 'getUpdates' failed! + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/error.js:85:15) + +error: Was there a typo in the url or port? + path: "https://api.telegram.org/bot8409280209:AAGwJTR7qilJfLQkZcXkpaRJMp997LMnsEM/getUpdates", + errno: 0, + code: "FailedToOpenSocket" + + +[grammY runner] Error while fetching updates: +[grammY runner] 80 | let msg = `Network request for '${method}' failed!`; +81 | if (isTelegramError(err)) +82 | msg += ` (${err.status}: ${err.statusText})`; +83 | if (sensitiveLogs && err instanceof Error) +84 | msg += ` ${err.message}`; +85 | throw new HttpError(msg, err); + ^ +HttpError: Network request for 'getUpdates' failed! + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/error.js:85:15) + +error: Unable to connect. Is the computer able to access the url? + path: "https://api.telegram.org/bot8409280209:AAGwJTR7qilJfLQkZcXkpaRJMp997LMnsEM/getUpdates", + errno: 0, + code: "ConnectionRefused" + + +[grammY runner] Error while fetching updates: +[grammY runner] 80 | let msg = `Network request for '${method}' failed!`; +81 | if (isTelegramError(err)) +82 | msg += ` (${err.status}: ${err.statusText})`; +83 | if (sensitiveLogs && err instanceof Error) +84 | msg += ` ${err.message}`; +85 | throw new HttpError(msg, err); + ^ +HttpError: Network request for 'getUpdates' failed! + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/error.js:85:15) + +error: Was there a typo in the url or port? + path: "https://api.telegram.org/bot8409280209:AAGwJTR7qilJfLQkZcXkpaRJMp997LMnsEM/getUpdates", + errno: 0, + code: "FailedToOpenSocket" + + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 80 | let msg = `Network request for '${method}' failed!`; +81 | if (isTelegramError(err)) +82 | msg += ` (${err.status}: ${err.statusText})`; +83 | if (sensitiveLogs && err instanceof Error) +84 | msg += ` ${err.message}`; +85 | throw new HttpError(msg, err); + ^ +HttpError: Network request for 'getUpdates' failed! + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/error.js:85:15) + +error: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch() + path: "https://api.telegram.org/bot8409280209:AAGwJTR7qilJfLQkZcXkpaRJMp997LMnsEM/getUpdates", + errno: 0, + code: "ECONNRESET" + + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + +[grammY runner] Error while fetching updates: +[grammY runner] 165 | function createTimeout(controller, seconds, method) { +166 | let handle = undefined; +167 | const promise = new Promise((_, reject) => { +168 | handle = setTimeout(() => { +169 | const msg = `Request to '${method}' timed out after ${seconds} seconds`; +170 | reject(new Error(msg)); + ^ +error: Request to 'getUpdates' timed out after 500 seconds + at (/Users/azreenjamal/pi-vs-claude-code/telegram-bot/node_modules/grammy/out/core/client.js:170:24) + diff --git a/telegram-bot/bot.log b/telegram-bot/bot.log index 43d485a..5e4a792 100644 --- a/telegram-bot/bot.log +++ b/telegram-bot/bot.log @@ -1,5 +1,134 @@ ๐Ÿš€ Bot starting... -โœ… @cr_management_smart_bot is live -๐Ÿ“ค [775071081] reply length: 504 -๐Ÿ”ง [775071081] run_command: top -bn1 | head -20 -๐Ÿ“ค [775071081] reply length: 585 +โœ… Bot is live (concurrent mode) +๐Ÿ“ค [775071081] 301 chars in 5.8s +๐Ÿ“ค [775071081] 480 chars in 5.7s +๐Ÿ“ค [775071081] 116 chars in 3.0s +๐Ÿ”ง [775071081] uname -a +๐Ÿ“ค [775071081] 212 chars in 7.6s +๐Ÿ”ง [775071081] systemctl list-units --type=service --state=running | head -20 +๐Ÿ”ง [775071081] ps aux | head -20 +๐Ÿ”ง [775071081] ps aux | grep -v "^\[" | grep -E "(nginx|apache|haproxy|mysql|redis|postgres|docker|incus)" | head -10 +๐Ÿ”ง [775071081] free -h && echo && df -h | head -5 +๐Ÿ“ค [775071081] 420 chars in 24.9s +๐Ÿ”ง [775071081] incus list +๐Ÿ“ค [775071081] 379 chars in 9.7s +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && ls -la +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && docker ps | grep enthuse +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && tail -20 logs/*.log | head -20 +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && ls logs/ | tail -5 +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && tail -10 logs/sync_20260227_185400.log +๐Ÿš€ Bot starting... +โœ… Bot is live (concurrent mode) +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && docker-compose ps +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && ls -la +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && docker ps | grep enthuse +๐Ÿ”ง [775071081] systemctl status docker +๐Ÿ”ง [775071081] ps aux | grep -E "(enthuse|launchgood)" | head -10 +๐Ÿ”ง [775071081] cd /opt/launchgood-sync && ls -la +๐Ÿ”ง [775071081] crontab -l | grep -E "(enthuse|launchgood)" +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2/logs && ls -la | tail -5 +๐Ÿ”ง [775071081] cd /opt/launchgood-sync/logs && ls -la | tail -5 +๐Ÿ”ง [775071081] tail -n 20 /opt/enthuse-db-sync-v2/logs/sync_20260227_185400.log +๐Ÿ“ค [775071081] 83 chars in 132.2s +๐Ÿš€ Bot starting... +โœ… Bot is live (concurrent mode) +๐Ÿ”ง [775071081] systemctl status enthuse-db-sync-v2 | head -10 +๐Ÿ”ง [775071081] ps aux | grep -E "(enthuse|launchgood)" | grep -v grep +๐Ÿ”ง [775071081] ls -la /opt/ | grep -E "(enthuse|launch)" +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && ls -la | head -5 +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && find . -name "*.log" | head -3 +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && ls -lt logs/ | head -5 +๐Ÿ”ง [775071081] cd /opt/enthuse-db-sync-v2 && tail -10 logs/sync_20260227_185400.log +๐Ÿ”ง [775071081] cd /opt/launchgood-sync && ls -lt logs/ | head -5 +๐Ÿ”ง [775071081] cd /opt/launchgood-sync && tail -5 logs/donations_20260227_185411.log +๐Ÿ”ง [775071081] crontab -l | grep -E "(enthuse|launch)" +๐Ÿš€ Bot starting... +โœ… Bot is live (concurrent mode) +๐Ÿš€ Bot starting... +โœ… Bot is live (concurrent mode) +๐Ÿ”ง [775071081] incus exec cr-server-new -- ps aux | grep -E "(enthuse|launchgood|charityright)" | grep -v grep +๐Ÿ”ง [775071081] incus exec cr-server-new -- ls -la /opt/ | grep sync +๐Ÿ”ง [775071081] incus exec cr-server-new -- crontab -l | grep sync +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -5 /opt/enthuse-db-sync-v2/logs/cron.log +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -5 /opt/launchgood-sync/logs/cron.log +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -5 /opt/charityright-sync/logs/cron.log +๐Ÿ“ค [775071081] 361 chars in 34.9s +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -20 /opt/charityright-sync/logs/cron.log +๐Ÿ”ง [775071081] incus exec cr-server-new -- ls -la /opt/charityright-sync/logs/ | head -10 +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -50 /opt/charityright-sync/logs/sync_$(ls /opt/charityright-sync/logs/sync_*.log | tail -1 | cut -d'/' -f5) +๐Ÿ”ง [775071081] incus exec cr-server-new -- ls /opt/charityright-sync/logs/sync_*.log | tail -1 +๐Ÿ”ง [775071081] incus exec cr-server-new -- find /opt/charityright-sync/logs/ -name "sync_*.log" -type f | tail -1 +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -20 /opt/charityright-sync/logs/sync_20260303_220001.log +๐Ÿ”ง [775071081] incus exec cr-server-new -- bash /opt/charityright-sync/run-sync.sh +๐Ÿ”ง [775071081] incus exec cr-server-new -- cat /opt/charityright-sync/run-sync.sh | head -20 +๐Ÿ”ง [775071081] incus exec cr-server-new -- grep -A 10 -B 10 "FAILED.*exit: 0" /opt/charityright-sync/run-sync.sh +๐Ÿ”ง [775071081] incus exec cr-server-new -- bash -c "cd /opt/charityright-sync && timeout 60 python3 sync.py" 2>&1 | head -20 +๐Ÿš€ Bot starting... +โœ… Bot is live (concurrent mode) +๐Ÿ”ง [775071081] incus list +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker ps -a | head -10 +๐Ÿ”ง [775071081] systemctl status haproxy | head -5 +๐Ÿ”ง [775071081] service haproxy status | head -5 +๐Ÿ”ง [775071081] ps aux | grep haproxy | grep -v grep +๐Ÿ”ง [775071081] incus exec cr-server-new -- ps aux | grep -E "(sync|cron)" | head -5 +๐Ÿ“ค [775071081] 394 chars in 89.3s +๐Ÿ”ง [775071081] incus exec cr-server-new -- crontab -l | grep -E "(enthuse|launchgood|charityright)" | head -10 +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -5 /opt/enthuse-db-sync-v2/logs/cron.log +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -5 /opt/launchgood-sync/logs/cron.log +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -5 /opt/charityright-sync/logs/cron.log +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker exec dokploy-migrated-cr-postgres-data psql -U postgres -d donation_warehouse -c "SELECT COUNT(*) as total_donations FROM donations;" 2>/dev/null +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker ps | grep postgres +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker exec dokploy-migrated-cr-postgres-data psql -U postgres -d donation_warehouse -c "SELECT COUNT(*) FROM donations;" | head -5 +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker exec dokploy-migrated-cr-postgres-data psql -U postgres -l | grep donation +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker exec dokploy-migrated-cr-postgres-data psql -U postgres -d donation_warehouse -c "\dt" | head -5 +๐Ÿ“ค [775071081] 558 chars in 237.2s +๐Ÿ”ง [775071081] incus exec cr-server-new -- cat /opt/charityright-sync/run-sync.sh +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -20 /opt/charityright-sync/run-sync.sh +๐Ÿ”ง [775071081] incus exec cr-server-new -- ls -la /opt/charityright-sync/logs/ | head -5 +๐Ÿ”ง [775071081] incus exec cr-server-new -- ls -la /opt/charityright-sync/logs/sync_* | tail -3 +๐Ÿ”ง [775071081] incus exec cr-server-new -- find /opt/charityright-sync/logs/ -name "sync_*" -type f | wc -l +๐Ÿ”ง [775071081] incus exec cr-server-new -- find /opt/charityright-sync/logs/ -name "sync_*" -type f | sort -t_ -k2 | tail -3 +๐Ÿ”ง [775071081] incus exec cr-server-new -- cat /opt/charityright-sync/logs/sync_20260304_160001.log +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker images | grep charityright +๐Ÿ”ง [775071081] incus exec cr-server-new -- ls -la /opt/charityright-sync/ +๐Ÿ”ง [775071081] incus exec cr-server-new -- cat /opt/charityright-sync/Dockerfile +๐Ÿ”ง [775071081] incus exec cr-server-new -- cd /opt/charityright-sync && docker build -t charityright-sync . +๐Ÿ”ง [775071081] incus exec cr-server-new -- bash -c "cd /opt/charityright-sync && docker build -t charityright-sync ." +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker images | grep charityright +๐Ÿ“ค [775071081] 155 chars in 29.8s +๐Ÿš€ Bot starting... +โœ… Bot is live (concurrent mode) +๐Ÿš€ Bot starting... +โœ… Bot is live (concurrent mode) +๐Ÿ”ง [775071081] incus exec cr-server-new -- ls -la /opt/charityright-sync/ +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -20 /opt/charityright-sync/logs/sync.log +๐Ÿ”ง [775071081] incus exec cr-server-new -- ls -la /opt/charityright-sync/logs/ +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -10 /opt/charityright-sync/logs/cron.log +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -20 /opt/charityright-sync/logs/sync_20260303_173815.log +๐Ÿ”ง [775071081] incus exec cr-server-new -- cat /opt/charityright-sync/run-sync.sh | head -30 +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -20 /opt/charityright-sync/run-sync.sh +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker run --rm --name charityright-sync-test --network dokploy-network charityright-sync 2>&1 | tail -10 +๐Ÿ”ง [775071081] incus exec cr-server-new -- timeout 30s docker run --rm --name charityright-sync-test --network dokploy-network charityright-sync; echo "Exit code: $?" +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker ps | grep charityright +๐Ÿ“ค [775071081] 647 chars in 220.2s +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -20 /opt/charityright-sync/sync_charityright.py +๐Ÿ”ง [775071081] incus exec cr-server-new -- grep -A5 -B5 "mysql_conn" /opt/charityright-sync/sync_charityright.py | tail -15 +๐Ÿ”ง [775071081] incus exec cr-server-new -- cp /opt/charityright-sync/sync_charityright.py /opt/charityright-sync/sync_charityright.py.backup +๐Ÿ”ง [775071081] incus exec cr-server-new -- sed -i 's/if __name__ == '\''__main__'\'':/ import sys\n sys.exit(0)\n\nif __name__ == '\''__main__'\'':/' /opt/charityright-sync/sync_charityright.py +๐Ÿ”ง [775071081] incus exec cr-server-new -- tail -10 /opt/charityright-sync/sync_charityright.py +๐Ÿ”ง [775071081] incus exec cr-server-new -- cd /opt/charityright-sync && docker build -t charityright-sync . +๐Ÿ”ง [775071081] incus exec cr-server-new -- bash -c "cd /opt/charityright-sync && docker build -t charityright-sync ." +๐Ÿ”ง [775071081] incus exec cr-server-new -- timeout 60s docker run --rm --name charityright-sync-test --network dokploy-network charityright-sync; echo "Exit code: $?" +๐Ÿ”ง [775071081] incus exec cr-server-new -- timeout 120s docker run --rm --name charityright-sync-test2 --network dokploy-network charityright-sync 2>&1 | tail -5; echo "Exit code: $?" +๐Ÿ”ง [775071081] incus exec cr-server-new -- bash /opt/charityright-sync/run-sync.sh 2>&1 | tail -10 +๐Ÿ“ค [775071081] 595 chars in 407.1s +๐Ÿ”ง [775071081] incus exec cr-server-new -- bash /opt/charityright-sync/run-sync.sh +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker exec dokploy-migrated-cr-postgres-data psql -U postgres -d donation_warehouse -c "SELECT state, started_at FROM sync_state WHERE source='charityright' ORDER BY started_at DESC LIMIT 1" +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker exec dokploy-migrated-cr-postgres-data psql -U postgres -d donation_warehouse -c "SELECT * FROM sync_state WHERE source='charityright' ORDER BY started_at DESC LIMIT 1" +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker exec dokploy-migrated-cr-postgres-data psql -U postgres -d donation_warehouse -c "\d sync_state" +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker exec dokploy-migrated-cr-postgres-data psql -U postgres -d donation_warehouse -c "SELECT * FROM sync_state WHERE key='charityright'" +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker ps | grep charityright +๐Ÿ”ง [775071081] incus exec cr-server-new -- bash -c "cd /opt/charityright-sync && timeout 180s docker run --rm --network dokploy-network charityright-sync 2>&1 | tail -10" +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker exec dokploy-migrated-cr-postgres-data psql -U postgres -d donation_warehouse -c "SELECT * FROM sync_state WHERE key='charityright'" +๐Ÿ”ง [775071081] incus exec cr-server-new -- docker exec dokploy-migrated-cr-postgres-data psql -U postgres -d donation_warehouse -c "SELECT * FROM sync_state" +๐Ÿ“ค [775071081] 651 chars in 370.7s diff --git a/telegram-bot/bun.lock b/telegram-bot/bun.lock index 2f78687..5855bbc 100644 --- a/telegram-bot/bun.lock +++ b/telegram-bot/bun.lock @@ -6,6 +6,7 @@ "name": "telegram-bot", "dependencies": { "@anthropic-ai/sdk": "^0.78.0", + "@grammyjs/runner": "^2.0.3", "grammy": "^1.41.0", }, "devDependencies": { @@ -21,6 +22,8 @@ "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], + "@grammyjs/runner": ["@grammyjs/runner@2.0.3", "", { "dependencies": { "abort-controller": "^3.0.0" }, "peerDependencies": { "grammy": "^1.13.1" } }, "sha512-nckmTs1dPWfVQteK9cxqxzE+0m1VRvluLWB8UgFzsjg62w3qthPJt0TYtJBEdG7OedvfQq4vnFAyE6iaMkR42A=="], + "@grammyjs/types": ["@grammyjs/types@3.25.0", "", {}, "sha512-iN9i5p+8ZOu9OMxWNcguojQfz4K/PDyMPOnL7PPCON+SoA/F8OKMH3uR7CVUkYfdNe0GCz8QOzAWrnqusQYFOg=="], "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], diff --git a/telegram-bot/index.ts b/telegram-bot/index.ts index c524713..8bc5785 100644 --- a/telegram-bot/index.ts +++ b/telegram-bot/index.ts @@ -1,4 +1,5 @@ import { Bot, Context } from "grammy"; +import { run } from "@grammyjs/runner"; import Anthropic from "@anthropic-ai/sdk"; import { readFileSync } from "fs"; import { resolve } from "path"; @@ -29,19 +30,83 @@ const anthropic = new Anthropic({ apiKey: ANTHROPIC_KEY }); // Per-user conversation history const conversations = new Map(); -const MAX_HISTORY = 30; +const MAX_HISTORY = 30; // raw entries before compression triggers +const MAX_TOKENS_ESTIMATE = 12000; // compress when estimated tokens exceed this -// โ”€โ”€โ”€ Auth middleware โ”€โ”€โ”€ +// Per-user abort controllers โ€” cancel stale requests +const activeRequests = new Map(); + +// โ”€โ”€โ”€ Context compression โ”€โ”€โ”€ +function estimateTokens(messages: Anthropic.MessageParam[]): number { + let chars = 0; + for (const msg of messages) { + if (typeof msg.content === "string") { + chars += msg.content.length; + } else if (Array.isArray(msg.content)) { + for (const block of msg.content) { + if ("text" in block) chars += (block as any).text.length; + else if ("content" in block) chars += String((block as any).content).length; + else chars += 100; // tool_use blocks etc + } + } + } + return Math.ceil(chars / 4); // rough chars-to-tokens +} + +async function compressHistory(userId: number, history: Anthropic.MessageParam[]): Promise { + if (history.length < 10 || estimateTokens(history) < MAX_TOKENS_ESTIMATE) return; + + // Keep last 6 messages intact (current conversation), compress the rest + const toCompress = history.slice(0, -6); + const toKeep = history.slice(-6); + + // Build a text summary of old messages + const summaryParts: string[] = []; + for (const msg of toCompress) { + if (typeof msg.content === "string") { + const role = msg.role === "user" ? "User" : "Assistant"; + summaryParts.push(`${role}: ${msg.content.slice(0, 200)}`); + } else if (Array.isArray(msg.content)) { + for (const block of msg.content) { + if ("text" in block && (block as any).text) { + const role = msg.role === "user" ? "User" : "Assistant"; + summaryParts.push(`${role}: ${(block as any).text.slice(0, 200)}`); + } else if ("content" in block && (block as any).type === "tool_result") { + summaryParts.push(`[cmd output: ${String((block as any).content).slice(0, 100)}]`); + } else if ((block as any).type === "tool_use") { + summaryParts.push(`[ran: ${JSON.stringify((block as any).input).slice(0, 100)}]`); + } + } + } + } + + const compressedSummary = summaryParts.join("\n").slice(0, 2000); + + // Replace history with compressed summary + recent messages + history.length = 0; + history.push({ + role: "user", + content: `[Previous conversation summary]\n${compressedSummary}\n[End summary โ€” continue from here]`, + }); + history.push({ + role: "assistant", + content: "Understood, I have the context from our previous conversation. How can I help?", + }); + history.push(...toKeep); + + console.log(`๐Ÿ“ฆ [${userId}] Compressed history: ${toCompress.length + toKeep.length} โ†’ ${history.length} entries (~${estimateTokens(history)} tokens)`); +} + +// โ”€โ”€โ”€ Auth โ”€โ”€โ”€ function isAuthorized(ctx: Context): boolean { - // Reject if no whitelist configured (fail-closed) if (ALLOWED_USERS.length === 0) return false; return ALLOWED_USERS.includes(ctx.from?.id || 0); } // โ”€โ”€โ”€ Rate limiting โ”€โ”€โ”€ const rateLimits = new Map(); -const RATE_LIMIT_WINDOW = 60_000; // 1 minute -const RATE_LIMIT_MAX = 15; // max messages per window +const RATE_LIMIT_WINDOW = 60_000; +const RATE_LIMIT_MAX = 15; function isRateLimited(userId: number): boolean { const now = Date.now(); @@ -51,22 +116,22 @@ function isRateLimited(userId: number): boolean { return timestamps.length > RATE_LIMIT_MAX; } -// โ”€โ”€โ”€ Blocked command patterns โ”€โ”€โ”€ +// โ”€โ”€โ”€ Blocked commands โ”€โ”€โ”€ const BLOCKED_PATTERNS = [ - /rm\s+-rf\s+\/(?!\w)/i, // rm -rf / - /mkfs\./i, // format disk - /dd\s+if=.*of=\/dev/i, // overwrite disk - /:\(\)\s*\{\s*:\|:/, // fork bomb + /rm\s+-rf\s+\/(?!\w)/i, + /mkfs\./i, + /dd\s+if=.*of=\/dev/i, + /:\(\)\s*\{\s*:\|:/, ]; function isDangerous(cmd: string): string | null { for (const pattern of BLOCKED_PATTERNS) { - if (pattern.test(cmd)) return `โ›” Blocked: matches dangerous pattern \`${pattern.source}\``; + if (pattern.test(cmd)) return `โ›” Blocked dangerous pattern`; } return null; } -// โ”€โ”€โ”€ SSH exec helper โ”€โ”€โ”€ +// โ”€โ”€โ”€ SSH exec โ”€โ”€โ”€ async function sshExec(command: string, timeoutSec = 30): Promise { const proc = Bun.spawn( ["ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=accept-new", @@ -83,60 +148,109 @@ async function sshExec(command: string, timeoutSec = 30): Promise { await proc.exited; const output = (stdout + stderr).trim(); - return output.length > 3500 ? output.slice(0, 3500) + "\n...(truncated)" : output; + return output.length > 2000 ? output.slice(0, 2000) + "\n...(truncated)" : output; +} + +// โ”€โ”€โ”€ Promise with timeout helper โ”€โ”€โ”€ +function withTimeout(promise: Promise, ms: number, label: string): Promise { + return Promise.race([ + promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error(`${label} timed out after ${ms / 1000}s`)), ms) + ), + ]); } // โ”€โ”€โ”€ System prompt โ”€โ”€โ”€ -const SYSTEM_PROMPT = `You are a DevOps assistant connected to a live server via Telegram. -You have access to run SSH commands on the server. You prioritize uptime. +const SYSTEM_PROMPT = `You are a DevOps assistant on Telegram. Connected to a live server via SSH. -Infrastructure context: ${infraContext} +ARCHITECTURE: +- The bot runs on a LOCAL LAPTOP (macOS). It is NOT on the server. +- run_command executes via SSH on the PRIMARY HOST (${SSH_HOST}). +- The primary host runs HAProxy and Incus (LXC containers). +- Docker runs INSIDE the Incus container "cr-server-new" (IP 10.213.16.224), NOT on the primary host. +- To run Docker/sync commands, you MUST wrap with: incus exec cr-server-new -- +- Example: incus exec cr-server-new -- docker ps +- Example: incus exec cr-server-new -- bash /opt/enthuse-db-sync-v2/run-sync.sh +- To query Postgres: incus exec cr-server-new -- docker exec dokploy-migrated-cr-postgres-data psql -U postgres -d donation_warehouse -c "SELECT ..." +- NEVER run docker commands directly on the primary host โ€” they won't work. + +KEY PATHS (inside cr-server-new): +- /opt/enthuse-db-sync-v2/ โ€” Enthuse sync +- /opt/launchgood-sync/ โ€” LaunchGood sync +- /opt/charityright-sync/ โ€” CharityRight sync +- Postgres container: dokploy-migrated-cr-postgres-data +- Docker network: dokploy-network + RULES: -- If the user asks to run a command, use the run_command tool. -- For destructive commands (rm -rf, DROP, reboot, etc.), WARN the user and ask for confirmation before executing. -- Keep responses concise โ€” this is Telegram, not a terminal. -- You can chain multiple commands in one SSH call using && or ; -- Format output with monospace blocks for readability.`; +- Be VERY concise โ€” this is Telegram, not a terminal. +- Keep command output short. Use | head, | tail, grep to limit output. +- For destructive commands, WARN and ask for confirmation first. +- ALWAYS end your response with a text message to the user. Never end on a tool call with no text summary.`; // โ”€โ”€โ”€ Claude tools โ”€โ”€โ”€ const tools: Anthropic.Tool[] = [ { name: "run_command", - description: "Execute a command on the server via SSH. Use for any server inspection, management, or debugging task.", + description: "Run a shell command on the server via SSH. Keep commands short and focused. Always pipe through head/tail/grep to limit output.", input_schema: { type: "object" as const, properties: { - command: { type: "string", description: "Shell command to run on the server" }, - timeout: { type: "number", description: "Timeout in seconds (default 30)" }, + command: { type: "string", description: "Shell command" }, }, required: ["command"], }, }, ]; -// โ”€โ”€โ”€ Process message through Claude โ”€โ”€โ”€ -async function chat(userId: number, message: string): Promise { - // Get or init conversation history +// โ”€โ”€โ”€ Chat with Claude โ€” with cancellation + timeout โ”€โ”€โ”€ +// Global typing context so SSH exec can trigger typing too +let activeCtx: Context | null = null; + +async function chat(userId: number, message: string, signal: AbortSignal): Promise { if (!conversations.has(userId)) conversations.set(userId, []); const history = conversations.get(userId)!; + // Snapshot history length before we start โ€” rollback to here on error + const historySnapshot = history.length; + history.push({ role: "user", content: message }); - // Trim history if too long + // Compress old history if getting too large + await compressHistory(userId, history); while (history.length > MAX_HISTORY) history.shift(); - let response = await anthropic.messages.create({ - model: "claude-sonnet-4-20250514", - max_tokens: 1024, - system: SYSTEM_PROMPT, - tools, - messages: history, - }); + try { + return await _chatInner(userId, history, signal); + } catch (err) { + // Rollback history to prevent corruption (dangling tool_use without tool_result) + history.splice(historySnapshot); + throw err; + } +} + +async function _chatInner(userId: number, history: Anthropic.MessageParam[], signal: AbortSignal): Promise { + let rounds = 0; + const MAX_ROUNDS = 10; + + let response = await withTimeout( + anthropic.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + system: SYSTEM_PROMPT, + tools, + messages: history, + }), + 90_000, + "Claude API" + ); + + while (response.stop_reason === "tool_use" && rounds < MAX_ROUNDS) { + if (signal.aborted) throw new Error("Cancelled โ€” new message received"); + rounds++; - // Tool use loop - while (response.stop_reason === "tool_use") { const assistantContent = response.content; history.push({ role: "assistant", content: assistantContent }); @@ -144,15 +258,22 @@ async function chat(userId: number, message: string): Promise { for (const block of assistantContent) { if (block.type === "tool_use") { - const input = block.input as { command: string; timeout?: number }; - console.log(`๐Ÿ”ง [${userId}] run_command: ${input.command}`); + if (signal.aborted) throw new Error("Cancelled"); + const input = block.input as { command: string }; + console.log(`๐Ÿ”ง [${userId}] ${input.command}`); + const danger = isDangerous(input.command); if (danger) { toolResults.push({ type: "tool_result", tool_use_id: block.id, content: danger, is_error: true }); continue; } try { - const output = await sshExec(input.command, input.timeout || 30); + // Show what's running + if (activeCtx) { + const shortCmd = input.command.length > 80 ? input.command.slice(0, 80) + "..." : input.command; + await activeCtx.reply(`โš™๏ธ \`${shortCmd}\``, { parse_mode: "Markdown" }).catch(() => {}); + } + const output = await sshExec(input.command); toolResults.push({ type: "tool_result", tool_use_id: block.id, content: output || "(no output)" }); } catch (err: any) { toolResults.push({ type: "tool_result", tool_use_id: block.id, content: `ERROR: ${err.message}`, is_error: true }); @@ -160,80 +281,134 @@ async function chat(userId: number, message: string): Promise { } } + // Always push tool_results immediately after assistant tool_use โ€” keeps history valid history.push({ role: "user", content: toolResults }); - response = await anthropic.messages.create({ - model: "claude-sonnet-4-20250514", - max_tokens: 1024, - system: SYSTEM_PROMPT, - tools, - messages: history, - }); + response = await withTimeout( + anthropic.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + system: SYSTEM_PROMPT, + tools, + messages: history, + }), + 90_000, + "Claude API" + ); + } + + if (response.stop_reason === "tool_use") { + // Max rounds โ€” force summary + const assistantContent = response.content; + history.push({ role: "assistant", content: assistantContent }); + + const toolResults: Anthropic.ToolResultBlockParam[] = []; + for (const block of assistantContent) { + if (block.type === "tool_use") { + toolResults.push({ type: "tool_result", tool_use_id: block.id, content: "Skipped โ€” max rounds reached. Summarize what you have so far.", is_error: true }); + } + } + history.push({ role: "user", content: toolResults }); + + const finalResponse = await withTimeout( + anthropic.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + system: SYSTEM_PROMPT, + messages: history, + }), + 60_000, + "Claude summary" + ); + + const text = finalResponse.content + .filter((b): b is Anthropic.TextBlock => b.type === "text") + .map((b) => b.text) + .join("\n") + .trim(); + history.push({ role: "assistant", content: text || "Done." }); + return text || "Done (max rounds reached)."; } - // Extract text response const text = response.content .filter((b): b is Anthropic.TextBlock => b.type === "text") .map((b) => b.text) - .join("\n"); + .join("\n") + .trim(); + + if (!text) { + // Claude gave no text โ€” ask it to summarize + history.push({ role: "assistant", content: response.content }); + history.push({ role: "user", content: "Now give me a brief summary of what you found. Text only, no tool calls." }); + const followUp = await withTimeout( + anthropic.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + system: SYSTEM_PROMPT, + messages: history, + }), + 60_000, + "Claude follow-up" + ); + const followUpText = followUp.content + .filter((b): b is Anthropic.TextBlock => b.type === "text") + .map((b) => b.text) + .join("\n") + .trim(); + history.push({ role: "assistant", content: followUpText || "Done." }); + return followUpText || "Done."; + } history.push({ role: "assistant", content: text }); - - return text || "(no response)"; + return text; } -// โ”€โ”€โ”€ Security: block groups, unauthorized, rate-limited โ”€โ”€โ”€ +// โ”€โ”€โ”€ Security middleware โ”€โ”€โ”€ bot.use(async (ctx, next) => { - // Block group/channel messages โ€” private only if (ctx.chat?.type !== "private") return; - - // Silent block for unauthorized users โ€” no response, no leak if (!isAuthorized(ctx)) { - console.log(`๐Ÿšซ Blocked user: ${ctx.from?.id} (@${ctx.from?.username})`); + console.log(`๐Ÿšซ Blocked: ${ctx.from?.id} (@${ctx.from?.username})`); return; } - - // Rate limit if (ctx.from && isRateLimited(ctx.from.id)) { - return ctx.reply("โณ Slow down โ€” rate limited."); + return ctx.reply("โณ Slow down."); } - await next(); }); -// โ”€โ”€โ”€ Bot handlers โ”€โ”€โ”€ -bot.command("start", (ctx) => { - return ctx.reply( - "๐Ÿค– *CR Management Bot*\n\n" + - "I'm connected to your server. You can:\n" + - "โ€ข Ask me anything about your infra\n" + - "โ€ข Ask me to run commands\n" + - "โ€ข `/status` โ€” quick server health\n" + - "โ€ข `/clear` โ€” reset conversation\n" + - "โ€ข `/ssh ` โ€” run a command directly\n", - { parse_mode: "Markdown" } - ); -}); +// โ”€โ”€โ”€ Commands โ”€โ”€โ”€ +bot.command("start", (ctx) => + ctx.reply( + "๐Ÿค– CR Management Bot\n\n" + + "โ€ข Chat naturally โ€” I understand your infra\n" + + "โ€ข /status โ€” server health\n" + + "โ€ข /ssh โ€” run command directly\n" + + "โ€ข /cancel โ€” stop current task\n" + + "โ€ข /clear โ€” reset conversation" + ) +); bot.command("status", async (ctx) => { - await ctx.reply("๐Ÿ” Checking server..."); + await ctx.reply("๐Ÿ” Checking..."); try { - const output = await sshExec("uptime && echo '---' && df -h / && echo '---' && free -h | head -2 && echo '---' && incus list --format csv -c ns 2>/dev/null"); - return ctx.reply(`\`\`\`\n${output}\n\`\`\``, { parse_mode: "Markdown" }); + const output = await withTimeout( + sshExec("uptime && echo --- && df -h / | tail -1 && echo --- && free -h | grep Mem && echo --- && incus list -f csv -c ns 2>/dev/null", 10), + 15_000, "SSH" + ); + return ctx.reply(output); } catch (err: any) { - return ctx.reply(`โŒ SSH failed: ${err.message}`); + return ctx.reply(`โŒ ${err.message}`); } }); bot.command("ssh", async (ctx) => { const cmd = ctx.match; - if (!cmd) return ctx.reply("Usage: `/ssh `", { parse_mode: "Markdown" }); + if (!cmd) return ctx.reply("Usage: /ssh "); const danger = isDangerous(cmd); if (danger) return ctx.reply(danger); - await ctx.reply(`โšก Running: \`${cmd}\``, { parse_mode: "Markdown" }); try { - const output = await sshExec(cmd); - return ctx.reply(`\`\`\`\n${output || "(no output)"}\n\`\`\``, { parse_mode: "Markdown" }); + const output = await withTimeout(sshExec(cmd, 10), 15_000, "SSH"); + return ctx.reply(output || "(no output)"); } catch (err: any) { return ctx.reply(`โŒ ${err.message}`); } @@ -241,37 +416,81 @@ bot.command("ssh", async (ctx) => { bot.command("clear", (ctx) => { conversations.delete(ctx.from!.id); - return ctx.reply("๐Ÿงน Conversation cleared."); + activeRequests.get(ctx.from!.id)?.abort(); + return ctx.reply("๐Ÿงน Cleared."); }); -// Free text โ†’ Claude -bot.on("message:text", async (ctx) => { +bot.command("cancel", (ctx) => { + const controller = activeRequests.get(ctx.from!.id); + if (controller) { + controller.abort(); + activeRequests.delete(ctx.from!.id); + return ctx.reply("๐Ÿ›‘ Cancelled."); + } + return ctx.reply("Nothing running."); +}); + +// โ”€โ”€โ”€ Free text โ†’ Claude (fire-and-forget, never blocks bot) โ”€โ”€โ”€ +bot.on("message:text", (ctx) => { const userId = ctx.from!.id; const msg = ctx.message!.text; - await ctx.replyWithChatAction("typing"); + // Cancel any in-flight request for this user + const prev = activeRequests.get(userId); + if (prev) { + prev.abort(); + // Clear corrupted history to avoid tool_result mismatch + conversations.delete(userId); + console.log(`โน๏ธ [${userId}] cancelled previous + cleared history`); + } + + const controller = new AbortController(); + activeRequests.set(userId, controller); + + // Fire and forget โ€” bot handler returns immediately + processMessage(ctx, userId, msg, controller); +}); + +async function processMessage(ctx: Context, userId: number, msg: string, controller: AbortController) { + const startTime = Date.now(); + activeCtx = ctx; + + // Keep typing every 3s โ€” Telegram typing indicator expires after ~5s + const typingInterval = setInterval(async () => { + if (controller.signal.aborted) return; + ctx.replyWithChatAction("typing").catch(() => {}); + }, 3000); + ctx.replyWithChatAction("typing").catch(() => {}); try { - const reply = await chat(userId, msg); - console.log(`๐Ÿ“ค [${userId}] reply length: ${reply.length}`); - // Split long messages (Telegram 4096 char limit) + const reply = await chat(userId, msg, controller.signal); + if (controller.signal.aborted) return; + + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + console.log(`๐Ÿ“ค [${userId}] ${reply.length} chars in ${elapsed}s`); + const chunks = reply.match(/[\s\S]{1,4000}/g) || ["(empty)"]; for (const chunk of chunks) { - try { - await ctx.reply(chunk); - } catch (sendErr: any) { - console.error(`โŒ [${userId}] send failed:`, sendErr.message); - await ctx.reply("โš ๏ธ Failed to format response. Check logs."); - } + await ctx.reply(chunk).catch(() => ctx.reply("โš ๏ธ Send failed.")); } } catch (err: any) { - console.error(`โŒ [${userId}]`, err); - await ctx.reply(`โŒ Error: ${err.message}`); + if (!controller.signal.aborted) { + console.error(`โŒ [${userId}]`, err.message); + await ctx.reply(`โŒ ${err.message}`).catch(() => {}); + } + } finally { + clearInterval(typingInterval); + activeCtx = null; + if (activeRequests.get(userId) === controller) activeRequests.delete(userId); } -}); +} -// โ”€โ”€โ”€ Launch โ”€โ”€โ”€ +// โ”€โ”€โ”€ Launch with concurrent runner (never blocks on slow handlers) โ”€โ”€โ”€ console.log("๐Ÿš€ Bot starting..."); -bot.start({ - onStart: (info) => console.log(`โœ… @${info.username} is live`), -}); +const runner = run(bot); +console.log(`โœ… Bot is live (concurrent mode)`); + +// Graceful shutdown +const stop = () => { runner.isRunning() && runner.stop(); }; +process.on("SIGINT", stop); +process.on("SIGTERM", stop); diff --git a/telegram-bot/package.json b/telegram-bot/package.json index 6dbdf83..6ee7801 100644 --- a/telegram-bot/package.json +++ b/telegram-bot/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.78.0", + "@grammyjs/runner": "^2.0.3", "grammy": "^1.41.0" } }