diff --git a/extensions/telegram/src/miniapp/command.test.ts b/extensions/telegram/src/miniapp/command.test.ts index af000c4189e7..380e04888b38 100644 --- a/extensions/telegram/src/miniapp/command.test.ts +++ b/extensions/telegram/src/miniapp/command.test.ts @@ -56,7 +56,7 @@ describe("createTelegramMiniAppDashboardCommand", () => { resolveTelegramMiniAppUrls.mockResolvedValue({ pageUrl: "https://host.tailnet.ts.net/__openclaw_tg_miniapp/", controlUiUrl: "https://host.tailnet.ts.net/openclaw", - gatewayUrl: "wss://host.tailnet.ts.net/openclaw", + gatewayUrl: "wss://host.tailnet.ts.net", }); const command = createTelegramMiniAppDashboardCommand( createTestPluginApi({ diff --git a/extensions/telegram/src/miniapp/command.ts b/extensions/telegram/src/miniapp/command.ts index f6ce57f83775..2b4af7253466 100644 --- a/extensions/telegram/src/miniapp/command.ts +++ b/extensions/telegram/src/miniapp/command.ts @@ -59,6 +59,8 @@ function currentConfig(api: OpenClawPluginApi): OpenClawConfig { } function isTelegramDirectCommand(ctx: PluginCommandContext): boolean { + // Parses OpenClaw's canonical telegram: / telegram:group: from/sessionKey encoding. + // DM-only because Telegram permits web_app inline buttons only in private chats. const from = ctx.from?.trim() ?? ""; const sessionKey = ctx.sessionKey?.trim() ?? ""; if (from.startsWith("telegram:group:") || sessionKey.includes(":telegram:group:")) { diff --git a/extensions/telegram/src/miniapp/owner.ts b/extensions/telegram/src/miniapp/owner.ts index 20add06ff09d..c35e70c1aa41 100644 --- a/extensions/telegram/src/miniapp/owner.ts +++ b/extensions/telegram/src/miniapp/owner.ts @@ -15,6 +15,8 @@ export async function isTelegramMiniAppOwner(params: { } const account = mergeTelegramAccountConfig(params.cfg, params.accountId); const allowFrom = [...(account.allowFrom ?? []), ...(params.cfg.commands?.ownerAllowFrom ?? [])]; + // Dashboard access is stricter than core senderIsOwner: wildcard and username + // allowFrom entries never grant the numeric-id match that mints an operator credential. const expanded = await expandTelegramAllowFromWithAccessGroups({ cfg: params.cfg, accountId: params.accountId, diff --git a/extensions/telegram/src/miniapp/page.test.ts b/extensions/telegram/src/miniapp/page.test.ts new file mode 100644 index 000000000000..3c290f0c022a --- /dev/null +++ b/extensions/telegram/src/miniapp/page.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from "vitest"; +import { renderTelegramMiniAppPage } from "./page.js"; + +describe("renderTelegramMiniAppPage", () => { + it("builds the dashboard redirect from the authenticated payload", () => { + const html = renderTelegramMiniAppPage({ accountId: "ops", scriptNonce: "nonce" }); + + expect(html).toContain('const accountId = "ops";'); + expect(html).toContain("new URL(payload.controlUiUrl)"); + expect(html).not.toContain("const controlUiUrl ="); + }); +}); diff --git a/extensions/telegram/src/miniapp/page.ts b/extensions/telegram/src/miniapp/page.ts index 598dd8e51b38..8a3a0ea3c3db 100644 --- a/extensions/telegram/src/miniapp/page.ts +++ b/extensions/telegram/src/miniapp/page.ts @@ -4,11 +4,9 @@ export const TELEGRAM_MINIAPP_EXPIRED_MESSAGE = export function renderTelegramMiniAppPage(params: { accountId: string; - controlUiUrl: string; scriptNonce: string; }): string { const accountId = JSON.stringify(params.accountId); - const controlUiUrl = JSON.stringify(params.controlUiUrl); const nonce = escapeHtml(params.scriptNonce); return ` @@ -33,7 +31,6 @@ export function renderTelegramMiniAppPage(params: {