-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat(onboard): support Telegram mention-only mode #2417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -406,6 +406,18 @@ let RECREATE_SANDBOX = false; | |
| // null means "use auto-allocation" (skip dashboard port check in preflight). | ||
| let _preflightDashboardPort: number | null = null; | ||
|
|
||
| // Read TELEGRAM_REQUIRE_MENTION (set either by the interactive mention prompt | ||
| // or by the user's shell) and map it to a boolean, or null when the env var | ||
| // is unset / invalid. Used at build time to bake groupPolicy into | ||
| // openclaw.json and at resume time to detect drift against the recorded | ||
| // session state. See #1737 and the CodeRabbit follow-up on #2417. | ||
| function computeTelegramRequireMention(): boolean | null { | ||
| const raw = process.env.TELEGRAM_REQUIRE_MENTION; | ||
| if (raw === "1") return true; | ||
| if (raw === "0") return false; | ||
| return null; | ||
|
Comment on lines
+414
to
+418
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject invalid Any value other than Proposed fix function computeTelegramRequireMention(): boolean | null {
const raw = process.env.TELEGRAM_REQUIRE_MENTION;
+ if (raw === undefined || raw === "") return null;
if (raw === "1") return true;
if (raw === "0") return false;
- return null;
+ console.error(" TELEGRAM_REQUIRE_MENTION must be set to 0 or 1.");
+ process.exit(1);
}🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| function isNonInteractive(): boolean { | ||
| return NON_INTERACTIVE || process.env.NEMOCLAW_NON_INTERACTIVE === "1"; | ||
| } | ||
|
|
@@ -1709,6 +1721,7 @@ function patchStagedDockerfile( | |
| messagingAllowedIds: LooseObject = {}, | ||
| discordGuilds: LooseObject = {}, | ||
| baseImageRef: string | null = null, | ||
| telegramConfig: LooseObject = {}, | ||
| ) { | ||
| const { providerKey, primaryModelRef, inferenceBaseUrl, inferenceApi, inferenceCompat } = | ||
| getSandboxInferenceConfig(model, provider, preferredInferenceApi); | ||
|
|
@@ -1850,6 +1863,12 @@ function patchStagedDockerfile( | |
| `ARG NEMOCLAW_DISCORD_GUILDS_B64=${encodeDockerJsonArg(discordGuilds)}`, | ||
| ); | ||
| } | ||
| if (telegramConfig && Object.keys(telegramConfig).length > 0) { | ||
| dockerfile = dockerfile.replace( | ||
| /^ARG NEMOCLAW_TELEGRAM_CONFIG_B64=.*$/m, | ||
| `ARG NEMOCLAW_TELEGRAM_CONFIG_B64=${encodeDockerJsonArg(telegramConfig)}`, | ||
| ); | ||
| } | ||
| fs.writeFileSync(dockerfilePath, dockerfile); | ||
| } | ||
|
|
||
|
|
@@ -4216,6 +4235,31 @@ async function createSandbox( | |
| }; | ||
| } | ||
| } | ||
| // Telegram mention-only mode — parity with Discord's requireMention. | ||
| // Off by default so existing sandboxes behave the same; opt-in via | ||
| // TELEGRAM_REQUIRE_MENTION=1 or the interactive prompt. See #1737. | ||
| const telegramConfig: { requireMention?: boolean } = {}; | ||
| if (enabledTokenEnvKeys.has("TELEGRAM_BOT_TOKEN")) { | ||
| const telegramRequireMention = computeTelegramRequireMention(); | ||
| if (telegramRequireMention !== null) { | ||
| telegramConfig.requireMention = telegramRequireMention; | ||
| } | ||
| } | ||
| // Persist the effective Telegram config into the session so a later resume | ||
| // can detect drift (TELEGRAM_REQUIRE_MENTION changed since last build) and | ||
| // force a sandbox recreate — otherwise the old groupPolicy would stay baked | ||
| // in. Mirrors the pattern used for webSearchConfig. See CodeRabbit on #2417. | ||
| if (typeof telegramConfig.requireMention === "boolean") { | ||
| onboardSession.updateSession((current) => { | ||
| current.telegramConfig = { requireMention: telegramConfig.requireMention as boolean }; | ||
| return current; | ||
| }); | ||
| } else { | ||
| onboardSession.updateSession((current) => { | ||
| current.telegramConfig = null; | ||
| return current; | ||
| }); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| // Pull the base image and resolve its digest so the Dockerfile is pinned to | ||
| // exactly what we just fetched. This prevents stale :latest tags from | ||
| // silently reusing a cached old image after NemoClaw upgrades (#1904). | ||
|
|
@@ -4254,6 +4298,7 @@ async function createSandbox( | |
| messagingAllowedIds, | ||
| discordGuilds, | ||
| resolved ? resolved.ref : null, | ||
| telegramConfig, | ||
| ); | ||
| // Only pass non-sensitive env vars to the sandbox. Credentials flow through | ||
| // OpenShell providers — the gateway injects them as placeholders and the L7 | ||
|
|
@@ -6088,17 +6133,23 @@ async function setupMessagingChannels(): Promise<string[]> { | |
| } | ||
| } | ||
| } | ||
| if (ch.requireMentionEnvKey && ch.serverIdEnvKey && process.env[ch.serverIdEnvKey]) { | ||
| const existingRequireMention = process.env[ch.requireMentionEnvKey]; | ||
| // Mention-control prompt: fires for any channel that exposes a | ||
| // requireMention env key. Discord gates the prompt behind a configured | ||
| // server ID (mention control only makes sense in a guild). Telegram | ||
| // has no serverIdEnvKey because mention control applies to every group | ||
| // the bot is added to, so the prompt always fires there. See #1737. | ||
| const requireMentionKey = ch.requireMentionEnvKey; | ||
| if (requireMentionKey && (!ch.serverIdEnvKey || Boolean(process.env[ch.serverIdEnvKey]))) { | ||
| const existingRequireMention = process.env[requireMentionKey]; | ||
| if (existingRequireMention === "0" || existingRequireMention === "1") { | ||
| const mode = existingRequireMention === "0" ? "all messages" : "@mentions only"; | ||
| console.log(` ✓ ${ch.name} — reply mode already set: ${mode}`); | ||
| } else { | ||
| console.log(` ${ch.requireMentionHelp}`); | ||
| const answer = (await prompt(" Reply only when @mentioned? [Y/n]: ")).trim().toLowerCase(); | ||
| process.env[ch.requireMentionEnvKey] = answer === "n" || answer === "no" ? "0" : "1"; | ||
| process.env[requireMentionKey] = answer === "n" || answer === "no" ? "0" : "1"; | ||
| const mode = | ||
| process.env[ch.requireMentionEnvKey] === "0" ? "all messages" : "@mentions only"; | ||
| process.env[requireMentionKey] === "0" ? "all messages" : "@mentions only"; | ||
| console.log(` ✓ ${ch.name} reply mode saved: ${mode}`); | ||
| } | ||
| } | ||
|
|
@@ -8179,9 +8230,27 @@ async function onboard(opts: OnboardOptions = {}): Promise<void> { | |
|
|
||
| const sandboxReuseState = getSandboxReuseState(sandboxName); | ||
| const webSearchConfigChanged = Boolean(session?.webSearchConfig) !== Boolean(webSearchConfig); | ||
| // Telegram mention-mode is baked into openclaw.json at sandbox build time, so | ||
| // changes to TELEGRAM_REQUIRE_MENTION only take effect after a rebuild. Treat | ||
| // a mismatch between the recorded config and the current env value as drift | ||
| // so the reuse path forces a recreate (mirrors webSearchConfigChanged). See | ||
| // #1737 and the CodeRabbit review on #2417. | ||
| // | ||
| // Compare *effective* modes — null and false both produce groupPolicy: open | ||
| // at config-generation time (default behavior), so they collapse to the same | ||
| // bucket here. Without this, a sandbox built before TELEGRAM_REQUIRE_MENTION | ||
| // existed (recordedTelegramRequireMention === null) would be reused with the | ||
| // old groupPolicy: open even after the user sets TELEGRAM_REQUIRE_MENTION=1, | ||
| // and vice versa. | ||
| const currentTelegramRequireMention = computeTelegramRequireMention(); | ||
| const recordedTelegramRequireMention = session?.telegramConfig?.requireMention ?? null; | ||
| const effectiveCurrent = currentTelegramRequireMention ?? false; | ||
| const effectiveRecorded = recordedTelegramRequireMention ?? false; | ||
| const telegramConfigChanged = effectiveCurrent !== effectiveRecorded; | ||
| const resumeSandbox = | ||
| resume && | ||
| !webSearchConfigChanged && | ||
| !telegramConfigChanged && | ||
| session?.steps?.sandbox?.status === "complete" && | ||
| sandboxReuseState === "ready"; | ||
| if (resumeSandbox) { | ||
|
|
@@ -8197,6 +8266,11 @@ async function onboard(opts: OnboardOptions = {}): Promise<void> { | |
| if (sandboxName) { | ||
| registry.removeSandbox(sandboxName); | ||
| } | ||
| } else if (telegramConfigChanged) { | ||
| note(" [resume] TELEGRAM_REQUIRE_MENTION changed; recreating sandbox."); | ||
| if (sandboxName) { | ||
| registry.removeSandbox(sandboxName); | ||
| } | ||
| } else if (sandboxReuseState === "not_ready") { | ||
| note( | ||
| ` [resume] Recorded sandbox '${sandboxName}' exists but is not ready; recreating it.`, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.