diff --git a/secure/README.md b/secure/README.md index b08de9a9e..692066b87 100644 --- a/secure/README.md +++ b/secure/README.md @@ -6,7 +6,7 @@ Your AI agent that runs on your infrastructure, answers only to you, and you can ## Why AssureBot? -| Full Moltbot | AssureBot | +| Full OpenClaw | AssureBot | |--------------|----------------| | 12+ channels | Telegram only | | File-based config | Env vars only | @@ -169,7 +169,7 @@ All webhooks are: ``` ┌────────────────────┐ ┌────────────────────┐ -│ moltbot-secure │────▶│ sandbox │ +│ assurebot │────▶│ sandbox │ │ (main container) │ │ (Docker sidecar) │ │ │ │ │ │ • Telegram bot │ │ • Isolated exec │ @@ -185,8 +185,8 @@ All webhooks are: ## License -MIT - Same as Moltbot. +MIT --- -**Full Moltbot**: [github.com/moltbot/moltbot](https://github.com/moltbot/moltbot) +Based on [OpenClaw](https://github.com/openclaw/openclaw) diff --git a/secure/audit.ts b/secure/audit.ts index 6351f1673..e869ae6cd 100644 --- a/secure/audit.ts +++ b/secure/audit.ts @@ -1,5 +1,5 @@ /** - * Moltbot Secure - Audit Logger + * AssureBot - Audit Logger * * Every interaction is logged for transparency and debugging. * Logs are append-only JSONL format. diff --git a/secure/config.ts b/secure/config.ts index c117aee09..6cb63edca 100644 --- a/secure/config.ts +++ b/secure/config.ts @@ -1,5 +1,5 @@ /** - * Moltbot Secure - Environment-only Configuration + * AssureBot - Environment-only Configuration * * All configuration via environment variables. * No config files, no filesystem secrets. @@ -177,7 +177,7 @@ export function loadSecureConfig(): SecureConfig { server: { port, host: optional("HOST", "0.0.0.0"), - gatewayToken: optional("MOLTBOT_GATEWAY_TOKEN", generateSecureToken()), + gatewayToken: optional("ASSUREBOT_GATEWAY_TOKEN", generateSecureToken()), }, }; } diff --git a/secure/index.ts b/secure/index.ts index 1b199b1cd..3e11624ee 100644 --- a/secure/index.ts +++ b/secure/index.ts @@ -56,33 +56,40 @@ async function main() { // Create conversation store const conversations = createConversationStore(); - // Create Telegram bot - console.log("[init] Creating Telegram bot..."); - const telegram = createTelegramBot({ - config, - audit, - agent, - conversations, - }); - - // Create webhook handler - console.log("[init] Creating webhook handler..."); - const webhooks = createWebhookHandler({ - config, - audit, - agent, - telegramBot: telegram.bot, - }); - // Create sandbox runner console.log("[init] Creating sandbox runner..."); const sandbox = createSandboxRunner(config, audit); const sandboxAvailable = await sandbox.isAvailable(); console.log(`[init] Sandbox available: ${sandboxAvailable}`); - // Create scheduler + // Create a placeholder bot for circular deps + // We'll create telegram, scheduler, and webhooks together + const { Bot } = await import("grammy"); + const bot = new Bot(config.telegram.botToken); + + // Create scheduler (needs bot for notifications) console.log("[init] Creating scheduler..."); const scheduler = createScheduler({ + config, + audit, + agent, + telegramBot: bot, + }); + + // Create Telegram bot handler (with sandbox and scheduler) + console.log("[init] Creating Telegram bot..."); + const telegram = createTelegramBot({ + config, + audit, + agent, + conversations, + sandbox, + scheduler, + }); + + // Create webhook handler + console.log("[init] Creating webhook handler..."); + const webhooks = createWebhookHandler({ config, audit, agent, diff --git a/secure/sandbox.ts b/secure/sandbox.ts index f7c087dd8..dda90f82d 100644 --- a/secure/sandbox.ts +++ b/secure/sandbox.ts @@ -1,5 +1,5 @@ /** - * Moltbot Secure - Sandbox Execution + * AssureBot - Sandbox Execution * * Isolated Docker container for code/script execution. * Security-first: no network, read-only root, resource limits. diff --git a/secure/scheduler.ts b/secure/scheduler.ts index c7539880f..a3cb95dfc 100644 --- a/secure/scheduler.ts +++ b/secure/scheduler.ts @@ -1,5 +1,5 @@ /** - * Moltbot Secure - Task Scheduler + * AssureBot - Task Scheduler * * Simple cron-like scheduler for recurring tasks. * Stores jobs in memory or optionally persists to file. diff --git a/secure/telegram.ts b/secure/telegram.ts index 4108404ad..4807ace6a 100644 --- a/secure/telegram.ts +++ b/secure/telegram.ts @@ -9,6 +9,8 @@ import { Bot, Context } from "grammy"; import type { SecureConfig } from "./config.js"; import type { AuditLogger } from "./audit.js"; import type { AgentCore, ConversationStore, ImageContent } from "./agent.js"; +import type { SandboxRunner } from "./sandbox.js"; +import type { Scheduler } from "./scheduler.js"; export type TelegramBot = { bot: Bot; @@ -21,6 +23,8 @@ export type TelegramDeps = { audit: AuditLogger; agent: AgentCore; conversations: ConversationStore; + sandbox?: SandboxRunner; + scheduler?: Scheduler; onWebhookMessage?: (userId: number, text: string) => void; }; @@ -37,7 +41,7 @@ function formatUsername(ctx: Context): string { } export function createTelegramBot(deps: TelegramDeps): TelegramBot { - const { config, audit, agent, conversations } = deps; + const { config, audit, agent, conversations, sandbox, scheduler } = deps; const bot = new Bot(config.telegram.botToken); // Error handler @@ -70,6 +74,9 @@ Commands: /start - Show this message /clear - Clear conversation history /status - Check bot status +/sandbox - Run code in sandbox +/schedule - Schedule a task +/tasks - List scheduled tasks /help - Show help Features: @@ -124,21 +131,183 @@ Features: - Chat with AI (text messages) - Image analysis (send photos) - Forward content for analysis -- Receive webhook notifications +- Run code in isolated sandbox +- Schedule recurring AI tasks 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 /help - This message Security: - Only authorized users can interact - All interactions are logged -- No data is sent to third parties (except AI provider)` +- Sandbox runs in isolated Docker (no network)` ); }); + // Command: /sandbox + bot.command("sandbox", async (ctx) => { + const userId = ctx.from?.id; + const username = formatUsername(ctx); + if (!userId || !isUserAllowed(userId, config.telegram.allowedUsers)) { + return; + } + + if (!sandbox) { + await ctx.reply("Sandbox is not configured."); + return; + } + + if (!config.sandbox.enabled) { + await ctx.reply("Sandbox is disabled."); + return; + } + + const code = ctx.message?.text?.replace(/^\/sandbox\s*/, "").trim() ?? ""; + if (!code) { + await ctx.reply("Usage: /sandbox \n\nExample: /sandbox echo Hello World"); + return; + } + + const startTime = Date.now(); + await ctx.replyWithChatAction("typing"); + + try { + const result = await sandbox.run(code); + const output = result.stdout || result.stderr || "(no output)"; + const status = result.exitCode === 0 ? "✓" : `✗ (exit ${result.exitCode})`; + const timeout = result.timedOut ? " [TIMED OUT]" : ""; + + await ctx.reply( + `**Sandbox Result** ${status}${timeout}\n\`\`\`\n${output.slice(0, 3000)}\n\`\`\`\nDuration: ${result.durationMs}ms`, + { parse_mode: "Markdown" } + ).catch(async () => { + await ctx.reply(`Sandbox Result ${status}${timeout}\n\n${output.slice(0, 3500)}\n\nDuration: ${result.durationMs}ms`); + }); + + audit.sandbox({ + command: code, + exitCode: result.exitCode, + durationMs: result.durationMs, + }); + } catch (err) { + const errorMsg = err instanceof Error ? err.message : String(err); + audit.error({ error: `Sandbox error: ${errorMsg}`, metadata: { userId, code } }); + await ctx.reply(`Sandbox error: ${errorMsg}`); + } + }); + + // Command: /schedule + bot.command("schedule", async (ctx) => { + const userId = ctx.from?.id; + if (!userId || !isUserAllowed(userId, config.telegram.allowedUsers)) { + return; + } + + if (!scheduler) { + await ctx.reply("Scheduler is not configured."); + return; + } + + if (!config.scheduler.enabled) { + await ctx.reply("Scheduler is disabled."); + return; + } + + // Parse: /schedule "*/5 * * * *" "Task Name" What to do + const text = ctx.message?.text?.replace(/^\/schedule\s*/, "").trim() ?? ""; + const match = text.match(/^"([^"]+)"\s+"([^"]+)"\s+(.+)$/s); + if (!match) { + await ctx.reply( + `Usage: /schedule "" "" + +Example: +/schedule "0 9 * * *" "Morning Brief" Give me a summary of what I should focus on today + +Cron format: minute hour day month weekday +- "0 9 * * *" = 9:00 AM daily +- "*/30 * * * *" = Every 30 minutes +- "0 0 * * 1" = Midnight on Mondays` + ); + return; + } + + const [, cronExpr, name, prompt] = match; + + try { + const taskId = scheduler.addTask({ + name, + schedule: cronExpr, + prompt, + enabled: true, + }); + await ctx.reply(`Task scheduled!\n\nID: ${taskId}\nName: ${name}\nSchedule: ${cronExpr}`); + } catch (err) { + const errorMsg = err instanceof Error ? err.message : String(err); + await ctx.reply(`Failed to schedule task: ${errorMsg}`); + } + }); + + // Command: /tasks + bot.command("tasks", async (ctx) => { + const userId = ctx.from?.id; + if (!userId || !isUserAllowed(userId, config.telegram.allowedUsers)) { + return; + } + + if (!scheduler) { + await ctx.reply("Scheduler is not configured."); + return; + } + + const tasks = scheduler.listTasks(); + if (tasks.length === 0) { + await ctx.reply("No scheduled tasks.\n\nUse /schedule to create one."); + return; + } + + const lines = tasks.map((t) => { + const status = t.enabled ? "✓" : "✗"; + const lastRun = t.lastRun ? t.lastRun.toISOString().slice(0, 16) : "never"; + return `${status} **${t.name}** (${t.id})\n ${t.schedule}\n Last: ${lastRun}`; + }); + + await ctx.reply(`**Scheduled Tasks**\n\n${lines.join("\n\n")}`, { parse_mode: "Markdown" }).catch(async () => { + await ctx.reply(`Scheduled Tasks\n\n${lines.join("\n\n").replace(/\*\*/g, "")}`); + }); + }); + + // Command: /deltask + bot.command("deltask", async (ctx) => { + const userId = ctx.from?.id; + if (!userId || !isUserAllowed(userId, config.telegram.allowedUsers)) { + return; + } + + if (!scheduler) { + await ctx.reply("Scheduler is not configured."); + return; + } + + const taskId = ctx.message?.text?.replace(/^\/deltask\s*/, "").trim() ?? ""; + if (!taskId) { + await ctx.reply("Usage: /deltask "); + return; + } + + if (scheduler.removeTask(taskId)) { + await ctx.reply(`Task ${taskId} deleted.`); + } else { + await ctx.reply(`Task ${taskId} not found.`); + } + }); + // Handle all text messages bot.on("message:text", async (ctx) => { const userId = ctx.from?.id; diff --git a/secure/webhooks.ts b/secure/webhooks.ts index 430d0e50d..707891612 100644 --- a/secure/webhooks.ts +++ b/secure/webhooks.ts @@ -1,5 +1,5 @@ /** - * Moltbot Secure - Webhook Receiver + * AssureBot - Webhook Receiver * * Authenticated webhook endpoint for external integrations. * Receives events from GitHub, Stripe, uptime monitors, etc. @@ -48,8 +48,8 @@ function extractToken(req: IncomingMessage, url: URL): { token: string; fromQuer return { token: authHeader.slice(7), fromQuery: false }; } - // Check X-Moltbot-Token header - const tokenHeader = req.headers["x-moltbot-token"]; + // Check X-AssureBot-Token header + const tokenHeader = req.headers["x-assurebot-token"]; if (typeof tokenHeader === "string") { return { token: tokenHeader, fromQuery: false }; }