diff --git a/secure/agent.ts b/secure/agent.ts index f9d5ac5aa..15a00f88a 100644 --- a/secure/agent.ts +++ b/secure/agent.ts @@ -46,7 +46,7 @@ const DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-20250514"; const DEFAULT_OPENAI_MODEL = "gpt-4o"; const DEFAULT_OPENROUTER_MODEL = "anthropic/claude-3.5-sonnet"; -const DEFAULT_SYSTEM_PROMPT = `You are a helpful AI assistant running as a secure, self-hosted bot. +const DEFAULT_SYSTEM_PROMPT = `You are AssureBot, a helpful AI assistant running as a secure Telegram bot. You are direct, concise, and helpful. You can: - Answer questions and have conversations @@ -54,7 +54,17 @@ You are direct, concise, and helpful. You can: - Help with coding and technical tasks - Summarize content and extract information -When you receive webhook notifications, summarize them helpfully for the user. +## Available Commands (tell users about these when relevant) +- /js - Run JavaScript +- /python - Run Python +- /ts - Run TypeScript +- /bash - Run shell commands +- /run - Run code in any language (python, js, ts, bash, rust, go, c, cpp, java, ruby, php) +- /status - Check bot status +- /clear - Clear conversation history + +When users ask to run or test code, guide them to use the appropriate command. +Example: "Use /js console.log('hello')" or "Try /python print('hello')" Be security-conscious: - Never reveal API keys, tokens, or secrets diff --git a/secure/personality.ts b/secure/personality.ts index 34fae4bb4..16dbd81b1 100644 --- a/secure/personality.ts +++ b/secure/personality.ts @@ -96,7 +96,7 @@ export async function createPersonality(storage: Storage): Promise async getSystemPrompt(userId: number): Promise { const profile = await loadUserProfile(userId); - let prompt = `You are ${traits.name}, a helpful AI assistant. + let prompt = `You are ${traits.name}, a helpful AI assistant running as a Telegram bot. ## Personality - Tone: ${profile.preferredTone} @@ -112,11 +112,28 @@ ${traits.expertiseAreas.map(e => `- ${e}`).join("\n")} - Recent topics: ${profile.recentTopics.length > 0 ? profile.recentTopics.slice(-3).join(", ") : "None yet"} ${profile.notes.length > 0 ? `- Notes: ${profile.notes.slice(-3).join("; ")}` : ""} +## Available Commands (you can tell users about these) +- /js - Run JavaScript code +- /python or /py - Run Python code +- /ts - Run TypeScript code +- /bash or /sh - Run shell commands +- /run - Run code in any supported language (python, javascript, typescript, bash, rust, go, c, cpp, java, ruby, php) +- /status - Check bot and sandbox status +- /clear - Clear conversation history +- /schedule "" "" - Schedule recurring AI tasks +- /tasks - List scheduled tasks +- /deltask - Delete a task + +When a user asks to run code, you can either: +1. Tell them to use the appropriate command (e.g., "Use /js console.log('hello')") +2. Just answer their question directly if they don't need to execute code + ## Guidelines - Be helpful, accurate, and security-conscious - Never reveal API keys, tokens, or secrets - Adapt to the user's communication style - Remember context from this conversation +- When users want to run code, guide them to use the right command ${traits.commonPhrases.length > 0 ? `- Phrases you like: ${traits.commonPhrases.join(", ")}` : ""} ${traits.avoidPhrases.length > 0 ? `- Avoid saying: ${traits.avoidPhrases.join(", ")}` : ""}`; diff --git a/secure/telegram.ts b/secure/telegram.ts index 871b5b0b7..33286c259 100644 --- a/secure/telegram.ts +++ b/secure/telegram.ts @@ -73,19 +73,25 @@ export function createTelegramBot(deps: TelegramDeps): TelegramBot { You are authorized to use this bot. -Commands: -/start - Show this message -/clear - Clear conversation history +Code Execution: +/js - Run JavaScript +/python - Run Python +/ts - Run TypeScript +/bash - Run shell commands +/run - Run any language + +Other Commands: /status - Check bot status -/sandbox - Run code in sandbox -/schedule - Schedule a task +/clear - Clear conversation history +/schedule - Schedule AI tasks /tasks - List scheduled tasks -/help - Show help +/help - Show full help Features: -- Send text messages to chat -- Send images for analysis -- Forward content for analysis` +- Chat with AI +- Image analysis (send photos) +- Document analysis (send PDFs) +- Code execution (15+ languages)` ); }); @@ -108,11 +114,15 @@ Features: } const history = conversations.get(userId); + const sandboxStatus = sandbox + ? `${sandbox.backend} (${await sandbox.isAvailable() ? "ready" : "unavailable"})` + : "not configured"; + await ctx.reply( `Status: - AI Provider: ${agent.provider} - Conversation: ${history.length} messages -- Sandbox: ${config.sandbox.enabled ? "enabled" : "disabled"} +- Sandbox: ${sandboxStatus} - Webhooks: ${config.webhooks.enabled ? "enabled" : "disabled"} - Scheduler: ${config.scheduler.enabled ? "enabled" : "disabled"}` ); @@ -130,27 +140,32 @@ Features: A secure, self-hosted AI assistant. -Features: -- Chat with AI (text messages) -- Image analysis (send photos) -- Forward content for analysis -- Run code in isolated sandbox -- Schedule recurring AI tasks +CODE EXECUTION: +/js - Run JavaScript +/python or /py - Run Python +/ts - Run TypeScript +/bash or /sh - Run shell +/run - Run any language -Commands: -/start - Welcome message -/clear - Clear conversation history -/status - Bot status -/sandbox - Run code in sandbox -/schedule "" "" - Schedule task -/tasks - List scheduled tasks -/deltask - Delete a task +Supported: python, javascript, typescript, bash, rust, go, c, cpp, java, ruby, php + +SCHEDULING: +/schedule "" "" +/tasks - List tasks +/deltask - Delete task + +Example: /schedule "0 9 * * *" "Morning" Good morning! + +OTHER: +/status - Bot & sandbox status +/clear - Clear conversation /help - This message -Security: -- Only authorized users can interact -- All interactions are logged -- Sandbox runs in isolated Docker (no network)` +FEATURES: +- Chat naturally with AI +- Send images for analysis +- Send PDFs/docs for analysis +- Code runs in isolated sandbox` ); }); @@ -206,6 +221,141 @@ Security: } }); + // Helper for language-specific code execution + async function runCodeCommand( + ctx: Context, + language: string, + commandName: string + ): Promise { + const userId = ctx.from?.id; + if (!userId || !isUserAllowed(userId, config.telegram.allowedUsers)) { + return; + } + + if (!sandbox) { + await ctx.reply("Sandbox is not configured."); + return; + } + + const isAvailable = await sandbox.isAvailable(); + if (!isAvailable) { + await ctx.reply(`Sandbox unavailable. Backend: ${sandbox.backend}`); + return; + } + + const code = ctx.message?.text?.replace(new RegExp(`^/${commandName}\\s*`), "").trim() ?? ""; + if (!code) { + await ctx.reply(`Usage: /${commandName} \n\nExample: /${commandName} console.log("Hello!")`); + return; + } + + await ctx.replyWithChatAction("typing"); + + try { + const result = await sandbox.runCode(language, code); + const output = result.stdout || result.stderr || "(no output)"; + const status = result.exitCode === 0 ? "✓" : `✗ (exit ${result.exitCode})`; + const timeout = result.timedOut ? " [TIMED OUT]" : ""; + const backend = sandbox.backend === "piston" ? " [Piston]" : ""; + + await ctx.reply( + `**${language}** ${status}${timeout}${backend}\n\`\`\`\n${output.slice(0, 3000)}\n\`\`\`\nDuration: ${result.durationMs}ms`, + { parse_mode: "Markdown" } + ).catch(async () => { + await ctx.reply(`${language} ${status}${timeout}${backend}\n\n${output.slice(0, 3500)}\n\nDuration: ${result.durationMs}ms`); + }); + + audit.sandbox({ + command: `[${language}] ${code.slice(0, 100)}`, + exitCode: result.exitCode, + durationMs: result.durationMs, + }); + } catch (err) { + const errorMsg = err instanceof Error ? err.message : String(err); + audit.error({ error: `Code execution error: ${errorMsg}`, metadata: { userId, language, code } }); + await ctx.reply(`Error: ${errorMsg}`); + } + } + + // Command: /js - Run JavaScript + bot.command("js", (ctx) => runCodeCommand(ctx, "javascript", "js")); + + // Command: /python - Run Python + bot.command("python", (ctx) => runCodeCommand(ctx, "python", "python")); + bot.command("py", (ctx) => runCodeCommand(ctx, "python", "py")); + + // Command: /ts - Run TypeScript + bot.command("ts", (ctx) => runCodeCommand(ctx, "typescript", "ts")); + + // Command: /bash - Run Bash + bot.command("bash", (ctx) => runCodeCommand(ctx, "bash", "bash")); + bot.command("sh", (ctx) => runCodeCommand(ctx, "bash", "sh")); + + // Command: /run - Run code in any supported language + bot.command("run", async (ctx) => { + const userId = ctx.from?.id; + if (!userId || !isUserAllowed(userId, config.telegram.allowedUsers)) { + return; + } + + if (!sandbox) { + await ctx.reply("Sandbox is not configured."); + return; + } + + const isAvailable = await sandbox.isAvailable(); + if (!isAvailable) { + await ctx.reply(`Sandbox unavailable. Backend: ${sandbox.backend}`); + return; + } + + const text = ctx.message?.text?.replace(/^\/run\s*/, "").trim() ?? ""; + const match = text.match(/^(\w+)\s+([\s\S]+)$/); + if (!match) { + await ctx.reply( + `Usage: /run + +Supported languages: +- javascript, js +- typescript, ts +- python, py +- bash, sh +- rust, go, c, cpp, java, ruby, php + +Example: /run python print("Hello!")` + ); + return; + } + + const [, language, code] = match; + await ctx.replyWithChatAction("typing"); + + try { + const result = await sandbox.runCode(language, code); + const output = result.stdout || result.stderr || "(no output)"; + const status = result.exitCode === 0 ? "✓" : `✗ (exit ${result.exitCode})`; + const timeout = result.timedOut ? " [TIMED OUT]" : ""; + const backend = sandbox.backend === "piston" ? " [Piston]" : ""; + + await ctx.reply( + `**${language}** ${status}${timeout}${backend}\n\`\`\`\n${output.slice(0, 3000)}\n\`\`\`\nDuration: ${result.durationMs}ms`, + { parse_mode: "Markdown" } + ).catch(async () => { + await ctx.reply(`${language} ${status}${timeout}${backend}\n\n${output.slice(0, 3500)}\n\nDuration: ${result.durationMs}ms`); + }); + + audit.sandbox({ + command: `[${language}] ${code.slice(0, 100)}`, + exitCode: result.exitCode, + durationMs: result.durationMs, + }); + } catch (err) { + const errorMsg = err instanceof Error ? err.message : String(err); + audit.error({ error: `Code execution error: ${errorMsg}`, metadata: { userId, language, code } }); + await ctx.reply(`Error: ${errorMsg}`); + } + }); + // Command: /schedule bot.command("schedule", async (ctx) => { const userId = ctx.from?.id;