Spaces:
Configuration error
Configuration error
| // 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(); | |