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?
|
## Why AssureBot?
|
||||||
|
|
||||||
| Full Moltbot | AssureBot |
|
| Full OpenClaw | AssureBot |
|
||||||
|--------------|----------------|
|
|--------------|----------------|
|
||||||
| 12+ channels | Telegram only |
|
| 12+ channels | Telegram only |
|
||||||
| File-based config | Env vars only |
|
| File-based config | Env vars only |
|
||||||
@ -169,7 +169,7 @@ All webhooks are:
|
|||||||
|
|
||||||
```
|
```
|
||||||
┌────────────────────┐ ┌────────────────────┐
|
┌────────────────────┐ ┌────────────────────┐
|
||||||
│ moltbot-secure │────▶│ sandbox │
|
│ assurebot │────▶│ sandbox │
|
||||||
│ (main container) │ │ (Docker sidecar) │
|
│ (main container) │ │ (Docker sidecar) │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ • Telegram bot │ │ • Isolated exec │
|
│ • Telegram bot │ │ • Isolated exec │
|
||||||
@ -185,8 +185,8 @@ All webhooks are:
|
|||||||
|
|
||||||
## License
|
## 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.
|
* Every interaction is logged for transparency and debugging.
|
||||||
* Logs are append-only JSONL format.
|
* Logs are append-only JSONL format.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Moltbot Secure - Environment-only Configuration
|
* AssureBot - Environment-only Configuration
|
||||||
*
|
*
|
||||||
* All configuration via environment variables.
|
* All configuration via environment variables.
|
||||||
* No config files, no filesystem secrets.
|
* No config files, no filesystem secrets.
|
||||||
@ -177,7 +177,7 @@ export function loadSecureConfig(): SecureConfig {
|
|||||||
server: {
|
server: {
|
||||||
port,
|
port,
|
||||||
host: optional("HOST", "0.0.0.0"),
|
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
|
// Create conversation store
|
||||||
const conversations = createConversationStore();
|
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
|
// Create sandbox runner
|
||||||
console.log("[init] Creating sandbox runner...");
|
console.log("[init] Creating sandbox runner...");
|
||||||
const sandbox = createSandboxRunner(config, audit);
|
const sandbox = createSandboxRunner(config, audit);
|
||||||
const sandboxAvailable = await sandbox.isAvailable();
|
const sandboxAvailable = await sandbox.isAvailable();
|
||||||
console.log(`[init] Sandbox available: ${sandboxAvailable}`);
|
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...");
|
console.log("[init] Creating scheduler...");
|
||||||
const scheduler = createScheduler({
|
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,
|
config,
|
||||||
audit,
|
audit,
|
||||||
agent,
|
agent,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Moltbot Secure - Sandbox Execution
|
* AssureBot - Sandbox Execution
|
||||||
*
|
*
|
||||||
* Isolated Docker container for code/script execution.
|
* Isolated Docker container for code/script execution.
|
||||||
* Security-first: no network, read-only root, resource limits.
|
* 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.
|
* Simple cron-like scheduler for recurring tasks.
|
||||||
* Stores jobs in memory or optionally persists to file.
|
* 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 { SecureConfig } from "./config.js";
|
||||||
import type { AuditLogger } from "./audit.js";
|
import type { AuditLogger } from "./audit.js";
|
||||||
import type { AgentCore, ConversationStore, ImageContent } from "./agent.js";
|
import type { AgentCore, ConversationStore, ImageContent } from "./agent.js";
|
||||||
|
import type { SandboxRunner } from "./sandbox.js";
|
||||||
|
import type { Scheduler } from "./scheduler.js";
|
||||||
|
|
||||||
export type TelegramBot = {
|
export type TelegramBot = {
|
||||||
bot: Bot;
|
bot: Bot;
|
||||||
@ -21,6 +23,8 @@ export type TelegramDeps = {
|
|||||||
audit: AuditLogger;
|
audit: AuditLogger;
|
||||||
agent: AgentCore;
|
agent: AgentCore;
|
||||||
conversations: ConversationStore;
|
conversations: ConversationStore;
|
||||||
|
sandbox?: SandboxRunner;
|
||||||
|
scheduler?: Scheduler;
|
||||||
onWebhookMessage?: (userId: number, text: string) => void;
|
onWebhookMessage?: (userId: number, text: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,7 +41,7 @@ function formatUsername(ctx: Context): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createTelegramBot(deps: TelegramDeps): TelegramBot {
|
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);
|
const bot = new Bot(config.telegram.botToken);
|
||||||
|
|
||||||
// Error handler
|
// Error handler
|
||||||
@ -70,6 +74,9 @@ Commands:
|
|||||||
/start - Show this message
|
/start - Show this message
|
||||||
/clear - Clear conversation history
|
/clear - Clear conversation history
|
||||||
/status - Check bot status
|
/status - Check bot status
|
||||||
|
/sandbox <code> - Run code in sandbox
|
||||||
|
/schedule <cron> <task> - Schedule a task
|
||||||
|
/tasks - List scheduled tasks
|
||||||
/help - Show help
|
/help - Show help
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
@ -124,21 +131,183 @@ Features:
|
|||||||
- Chat with AI (text messages)
|
- Chat with AI (text messages)
|
||||||
- Image analysis (send photos)
|
- Image analysis (send photos)
|
||||||
- Forward content for analysis
|
- Forward content for analysis
|
||||||
- Receive webhook notifications
|
- Run code in isolated sandbox
|
||||||
|
- Schedule recurring AI tasks
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
/start - Welcome message
|
/start - Welcome message
|
||||||
/clear - Clear conversation history
|
/clear - Clear conversation history
|
||||||
/status - Bot status
|
/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
|
/help - This message
|
||||||
|
|
||||||
Security:
|
Security:
|
||||||
- Only authorized users can interact
|
- Only authorized users can interact
|
||||||
- All interactions are logged
|
- 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
|
// Handle all text messages
|
||||||
bot.on("message:text", async (ctx) => {
|
bot.on("message:text", async (ctx) => {
|
||||||
const userId = ctx.from?.id;
|
const userId = ctx.from?.id;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Moltbot Secure - Webhook Receiver
|
* AssureBot - Webhook Receiver
|
||||||
*
|
*
|
||||||
* Authenticated webhook endpoint for external integrations.
|
* Authenticated webhook endpoint for external integrations.
|
||||||
* Receives events from GitHub, Stripe, uptime monitors, etc.
|
* 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 };
|
return { token: authHeader.slice(7), fromQuery: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check X-Moltbot-Token header
|
// Check X-AssureBot-Token header
|
||||||
const tokenHeader = req.headers["x-moltbot-token"];
|
const tokenHeader = req.headers["x-assurebot-token"];
|
||||||
if (typeof tokenHeader === "string") {
|
if (typeof tokenHeader === "string") {
|
||||||
return { token: tokenHeader, fromQuery: false };
|
return { token: tokenHeader, fromQuery: false };
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user