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:
Claude 2026-01-30 07:04:49 +00:00
parent 8f0a3c662a
commit a44d683dd7
No known key found for this signature in database
8 changed files with 210 additions and 34 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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()),
},
};
}

View File

@ -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,

View File

@ -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.

View File

@ -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.

View 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;

View File

@ -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 };
}