diff --git a/scripts/telegram-bridge.js b/scripts/telegram-bridge.js index c51a5529a4..76613a5290 100755 --- a/scripts/telegram-bridge.js +++ b/scripts/telegram-bridge.js @@ -12,7 +12,7 @@ * Env: * TELEGRAM_BOT_TOKEN — from @BotFather * NVIDIA_API_KEY — for inference - * SANDBOX_NAME — sandbox name (default: nemoclaw) + * SANDBOX_NAME — sandbox name (default: from registry, or "my-assistant") * ALLOWED_CHAT_IDS — comma-separated Telegram chat IDs to accept (optional, accepts all if unset) */ @@ -29,8 +29,6 @@ if (!OPENSHELL) { const TOKEN = process.env.TELEGRAM_BOT_TOKEN; const API_KEY = process.env.NVIDIA_API_KEY; -const SANDBOX = process.env.SANDBOX_NAME || "nemoclaw"; -try { validateName(SANDBOX, "SANDBOX_NAME"); } catch (e) { console.error(e.message); process.exit(1); } const ALLOWED_CHATS = process.env.ALLOWED_CHAT_IDS ? process.env.ALLOWED_CHAT_IDS.split(",").map((s) => s.trim()) : null; @@ -38,6 +36,25 @@ const ALLOWED_CHATS = process.env.ALLOWED_CHAT_IDS if (!TOKEN) { console.error("TELEGRAM_BOT_TOKEN required"); process.exit(1); } if (!API_KEY) { console.error("NVIDIA_API_KEY required"); process.exit(1); } +/** + * Resolve the sandbox name. Priority: + * 1. SANDBOX_NAME env var (explicit override) + * 2. The default sandbox from the NemoClaw registry + * 3. Fallback to "my-assistant" (matches onboard default) + */ +function resolveSandboxName() { + if (process.env.SANDBOX_NAME) return process.env.SANDBOX_NAME; + try { + const registry = require("../bin/lib/registry"); + const def = registry.getDefault(); + if (def) return def; + } catch {} + return "my-assistant"; +} + +const SANDBOX = resolveSandboxName(); +try { validateName(SANDBOX, "SANDBOX_NAME"); } catch (e) { console.error(e.message); process.exit(1); } + let offset = 0; const activeSessions = new Map(); // chatId → message history diff --git a/test/telegram-bridge-sandbox-name.test.js b/test/telegram-bridge-sandbox-name.test.js new file mode 100644 index 0000000000..860f8644b2 --- /dev/null +++ b/test/telegram-bridge-sandbox-name.test.js @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); + +const ROOT = path.resolve(__dirname, ".."); +const SCRIPT = fs.readFileSync(path.join(ROOT, "scripts", "telegram-bridge.js"), "utf-8"); + +function extractFunctionSource(source, name) { + const signature = `function ${name}()`; + const start = source.indexOf(signature); + assert.notEqual(start, -1, `expected source to include ${signature}`); + + const bodyStart = source.indexOf("{", start); + assert.notEqual(bodyStart, -1, `expected ${name} to have a function body`); + + let depth = 0; + for (let i = bodyStart; i < source.length; i += 1) { + const char = source[i]; + if (char === "{") { + depth += 1; + } else if (char === "}") { + depth -= 1; + if (depth === 0) { + return source.slice(start, i + 1); + } + } + } + + assert.fail(`expected ${name}() to have balanced braces`); +} + +const RESOLVE_SANDBOX_NAME_SOURCE = extractFunctionSource(SCRIPT, "resolveSandboxName"); + +function resolveSandboxNameWith({ env = {}, registryDefault, registryThrows = false } = {}) { + assert.ok(RESOLVE_SANDBOX_NAME_SOURCE, "expected telegram bridge to define resolveSandboxName()"); + + const processStub = { env: { ...env } }; + const requireStub = (specifier) => { + if (specifier !== "../bin/lib/registry") { + throw new Error(`unexpected require: ${specifier}`); + } + if (registryThrows) { + throw new Error("registry unavailable"); + } + return { getDefault: () => registryDefault }; + }; + + const resolveSandboxName = new Function( + "process", + "require", + `${RESOLVE_SANDBOX_NAME_SOURCE}\nreturn resolveSandboxName;`, + )(processStub, requireStub); + + return resolveSandboxName(); +} + +describe("telegram bridge sandbox resolution", () => { + it("prefers SANDBOX_NAME when explicitly set", () => { + assert.equal( + resolveSandboxNameWith({ + env: { SANDBOX_NAME: "from-env" }, + registryDefault: "from-registry", + }), + "from-env", + ); + }); + + it("reads the default sandbox from the registry when env is unset", () => { + assert.equal( + resolveSandboxNameWith({ registryDefault: "from-registry" }), + "from-registry", + ); + }); + + it("falls back to my-assistant when no explicit or registered sandbox exists", () => { + assert.equal( + resolveSandboxNameWith({ registryThrows: true }), + "my-assistant", + ); + }); +});