feat: wire up sandbox/scheduler commands + complete AssureBot rebrand
- Add /sandbox, /schedule, /tasks, /deltask commands to telegram bot - Wire sandbox and scheduler dependencies through to telegram handler - Complete AssureBot rebrand across all files (audit, config, webhooks, etc.) - Update secure/README.md with correct branding - Update X-AssureBot-Token header for webhooks - Update ASSUREBOT_GATEWAY_TOKEN env var https://claude.ai/code/session_015VqJ7gN4vaxtYfYc92UjLs
This commit is contained in:
parent
8f0a3c662a
commit
a44d683dd7
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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()),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 <code> - Run code in sandbox
|
||||
/schedule <cron> <task> - 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 <code> - Run code in sandbox
|
||||
/schedule "<cron>" "<name>" <prompt> - Schedule task
|
||||
/tasks - List scheduled tasks
|
||||
/deltask <id> - 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 <code>
|
||||
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 <code>\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 <cron> <name> <prompt>
|
||||
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 "<cron>" "<name>" <prompt>
|
||||
|
||||
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 <id>
|
||||
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 <task_id>");
|
||||
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;
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user