INIclaw / scripts /telegram-bridge.js
NitishStark's picture
Upload folder using huggingface_hub
0722e92 verified
#!/usr/bin/env node
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
/**
* Telegram bridge for ini_claw. bridge.
*
* Messages from Telegram are forwarded to the OpenClaw agent running
* inside the sandbox. When the agent needs external access, the
* OpenShell TUI lights up for approval. Responses go back to Telegram.
*
* Env:
* TELEGRAM_BOT_TOKEN β€” from @BotFather
* NVIDIA_API_KEY β€” for inference
* SANDBOX_NAME β€” sandbox name (default: ini_claw)
* ALLOWED_CHAT_IDS β€” comma-separated Telegram chat IDs to accept (optional, accepts all if unset)
*/
const https = require("https");
const { execSync, spawn } = require("child_process");
const TOKEN = process.env.TELEGRAM_BOT_TOKEN;
const API_KEY = process.env.NVIDIA_API_KEY;
const SANDBOX = process.env.SANDBOX_NAME || "ini_claw";
const ALLOWED_CHATS = process.env.ALLOWED_CHAT_IDS
? process.env.ALLOWED_CHAT_IDS.split(",").map((s) => s.trim())
: null;
if (!TOKEN) { console.error("TELEGRAM_BOT_TOKEN required"); process.exit(1); }
if (!API_KEY) { console.error("NVIDIA_API_KEY required"); process.exit(1); }
let offset = 0;
const activeSessions = new Map(); // chatId β†’ message history
// ── Telegram API helpers ──────────────────────────────────────────
function tgApi(method, body) {
return new Promise((resolve, reject) => {
const data = JSON.stringify(body);
const req = https.request(
{
hostname: "api.telegram.org",
path: `/bot${TOKEN}/${method}`,
method: "POST",
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(data) },
},
(res) => {
let buf = "";
res.on("data", (c) => (buf += c));
res.on("end", () => {
try { resolve(JSON.parse(buf)); } catch { resolve({ ok: false, error: buf }); }
});
},
);
req.on("error", reject);
req.write(data);
req.end();
});
}
async function sendMessage(chatId, text, replyTo) {
// Telegram max message length is 4096
const chunks = [];
for (let i = 0; i < text.length; i += 4000) {
chunks.push(text.slice(i, i + 4000));
}
for (const chunk of chunks) {
await tgApi("sendMessage", {
chat_id: chatId,
text: chunk,
reply_to_message_id: replyTo,
parse_mode: "Markdown",
}).catch(() =>
// Retry without markdown if it fails (unbalanced formatting)
tgApi("sendMessage", { chat_id: chatId, text: chunk, reply_to_message_id: replyTo }),
);
}
}
async function sendTyping(chatId) {
await tgApi("sendChatAction", { chat_id: chatId, action: "typing" }).catch(() => {});
}
// ── Run agent inside sandbox ──────────────────────────────────────
function runAgentInSandbox(message, sessionId) {
return new Promise((resolve) => {
const sshConfig = execSync(`openshell sandbox ssh-config ${SANDBOX}`, { encoding: "utf-8" });
// Write temp ssh config
const confPath = `/tmp/iniclaw-tg-ssh-${sessionId}.conf`;
require("fs").writeFileSync(confPath, sshConfig);
const escaped = message.replace(/'/g, "'\\''");
const cmd = `export NVIDIA_API_KEY='${API_KEY}' && iniclaw-start openclaw agent --agent main --local -m '${escaped}' --session-id 'tg-${sessionId}'`;
const proc = spawn("ssh", ["-T", "-F", confPath, `openshell-${SANDBOX}`, cmd], {
timeout: 120000,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
proc.stdout.on("data", (d) => (stdout += d.toString()));
proc.stderr.on("data", (d) => (stderr += d.toString()));
proc.on("close", (code) => {
try { require("fs").unlinkSync(confPath); } catch {}
// Extract the actual agent response β€” skip setup lines
const lines = stdout.split("\n");
const responseLines = lines.filter(
(l) =>
!l.startsWith("Setting up IniClaw") &&
!l.startsWith("[plugins]") &&
!l.startsWith("(node:") &&
!l.includes("IniClaw ready") &&
!l.includes("IniClaw registered") &&
!l.includes("openclaw agent") &&
!l.includes("β”Œβ”€") &&
!l.includes("β”‚ ") &&
!l.includes("└─") &&
l.trim() !== "",
);
const response = responseLines.join("\n").trim();
if (response) {
resolve(response);
} else if (code !== 0) {
resolve(`Agent exited with code ${code}. ${stderr.trim().slice(0, 500)}`);
} else {
resolve("(no response)");
}
});
proc.on("error", (err) => {
resolve(`Error: ${err.message}`);
});
});
}
// ── Poll loop ─────────────────────────────────────────────────────
async function poll() {
try {
const res = await tgApi("getUpdates", { offset, timeout: 30 });
if (res.ok && res.result?.length > 0) {
for (const update of res.result) {
offset = update.update_id + 1;
const msg = update.message;
if (!msg?.text) continue;
const chatId = String(msg.chat.id);
// Access control
if (ALLOWED_CHATS && !ALLOWED_CHATS.includes(chatId)) {
console.log(`[ignored] chat ${chatId} not in allowed list`);
continue;
}
const userName = msg.from?.first_name || "someone";
console.log(`[${chatId}] ${userName}: ${msg.text}`);
// Handle /start
if (msg.text === "/start") {
await sendMessage(
chatId,
"πŸ¦€ *IniClaw* β€” powered by Nemotron 3 Super 120B\n\n" +
"Send me a message and I'll run it through the OpenClaw agent " +
"inside an OpenShell sandbox.\n\n" +
"If the agent needs external access, the TUI will prompt for approval.",
msg.message_id,
);
continue;
}
// Handle /reset
if (msg.text === "/reset") {
activeSessions.delete(chatId);
await sendMessage(chatId, "Session reset.", msg.message_id);
continue;
}
// Send typing indicator
await sendTyping(chatId);
// Keep a typing indicator going while agent runs
const typingInterval = setInterval(() => sendTyping(chatId), 4000);
try {
const response = await runAgentInSandbox(msg.text, chatId);
clearInterval(typingInterval);
console.log(`[${chatId}] agent: ${response.slice(0, 100)}...`);
await sendMessage(chatId, response, msg.message_id);
} catch (err) {
clearInterval(typingInterval);
await sendMessage(chatId, `Error: ${err.message}`, msg.message_id);
}
}
}
} catch (err) {
console.error("Poll error:", err.message);
}
// Continue polling
setTimeout(poll, 100);
}
// ── Main ──────────────────────────────────────────────────────────
async function main() {
const me = await tgApi("getMe", {});
if (!me.ok) {
console.error("Failed to connect to Telegram:", JSON.stringify(me));
process.exit(1);
}
console.log("");
console.log(" β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”");
console.log(" β”‚ IniClaw Telegram Bridge β”‚");
console.log(" β”‚ β”‚");
console.log(` β”‚ Bot: @${(me.result.username + " ").slice(0, 37)}β”‚`);
console.log(" β”‚ Sandbox: " + (SANDBOX + " ").slice(0, 40) + "β”‚");
console.log(" β”‚ Model: nvidia/nemotron-3-super-120b-a12b β”‚");
console.log(" β”‚ β”‚");
console.log(" β”‚ Messages are forwarded to the OpenClaw agent β”‚");
console.log(" β”‚ inside the sandbox. Run 'openshell term' in β”‚");
console.log(" β”‚ another terminal to monitor + approve egress. β”‚");
console.log(" β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜");
console.log("");
poll();
}
main();