-
Notifications
You must be signed in to change notification settings - Fork 88
M2: Add proactive outbound Telegram messaging primitive #6222
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,128 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Telegram Bot messaging provider adapter. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Enables proactive outbound messaging to Telegram chats via the gateway's | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * /deliver/telegram endpoint. Unlike Slack/Gmail which use direct API calls | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * with OAuth tokens, Telegram delivery is proxied through the gateway which | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * owns the bot token and handles Telegram API retries. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * The `token` parameter in MessagingProvider methods is unused for Telegram | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * because delivery is authenticated via the gateway's bearer token, not | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * a per-user OAuth token. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { MessagingProvider } from '../../provider.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Conversation, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Message, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SearchResult, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SendResult, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ConnectionInfo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ListOptions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HistoryOptions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SearchOptions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SendOptions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '../../provider-types.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getSecureKey } from '../../../security/secure-keys.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { readHttpToken } from '../../../util/platform.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as telegram from './client.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Resolve the local gateway base URL from GATEWAY_PORT (default 7830). */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function getGatewayUrl(): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const port = Number(process.env.GATEWAY_PORT) || 7830; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `http://127.0.0.1:${port}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Read the runtime HTTP bearer token used to authenticate with the gateway. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function getBearerToken(): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const token = readHttpToken(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!token) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('No runtime HTTP bearer token available — is the daemon running?'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return token; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Read the Telegram bot token from the credential vault. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function getBotToken(): string | undefined { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return getSecureKey('credential:telegram:bot_token'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const telegramBotMessagingProvider: MessagingProvider = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: 'telegram', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| displayName: 'Telegram', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| credentialService: 'telegram', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| capabilities: new Set(['send']), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Custom connectivity check. The standard registry check looks for | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * credential:telegram:access_token, but the Telegram bot token is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * stored as credential:telegram:bot_token. This method lets the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * registry detect that Telegram credentials exist. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isConnected(): boolean { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return getBotToken() !== undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async testConnection(_token: string): Promise<ConnectionInfo> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const botToken = getBotToken(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!botToken) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connected: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user: 'unknown', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| platform: 'telegram', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata: { error: 'No bot token found. Run the telegram-setup skill.' }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const resp = await telegram.getMe(botToken); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!resp.ok || !resp.result) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connected: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user: 'unknown', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| platform: 'telegram', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata: { error: resp.description ?? 'getMe failed' }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+77
to
+85
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. 🟡 The Detailed ExplanationIn So when the Telegram API returns an error (e.g., invalid bot token → 401), Instead, the Impact: When a bot token is invalid,
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connected: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user: resp.result.username ?? resp.result.first_name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| platform: 'telegram', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| botId: resp.result.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| botUsername: resp.result.username, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| botName: resp.result.first_name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async sendMessage(_token: string, conversationId: string, text: string, _options?: SendOptions): Promise<SendResult> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const gatewayUrl = getGatewayUrl(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const bearerToken = getBearerToken(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await telegram.sendMessage(gatewayUrl, bearerToken, conversationId, text); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: `tg-${Date.now()}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timestamp: Date.now(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conversationId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Telegram Bot API does not support listing conversations. Bots only | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // interact with chats where users have initiated contact or the bot | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // has been added to a group. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async listConversations(_token: string, _options?: ListOptions): Promise<Conversation[]> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Telegram Bot API does not provide message history retrieval. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async getHistory(_token: string, _conversationId: string, _options?: HistoryOptions): Promise<Message[]> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Telegram Bot API does not support message search. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async search(_token: string, _query: string, _options?: SearchOptions): Promise<SearchResult> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { total: 0, messages: [], hasMore: false }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| /** | ||
| * Low-level Telegram operations. | ||
| * | ||
| * Outbound message delivery routes through the gateway's /deliver/telegram | ||
| * endpoint, which handles bot token management and Telegram API retries. | ||
| * Connection verification calls the Telegram Bot API directly with the | ||
| * stored bot token. | ||
| */ | ||
|
|
||
| import type { TelegramGetMeResponse } from './types.js'; | ||
|
|
||
| const TELEGRAM_API_BASE = 'https://api.telegram.org'; | ||
| const DELIVERY_TIMEOUT_MS = 30_000; | ||
|
|
||
| export class TelegramApiError extends Error { | ||
| constructor( | ||
| public readonly status: number, | ||
| message: string, | ||
| ) { | ||
| super(message); | ||
| this.name = 'TelegramApiError'; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Verify a bot token by calling Telegram's getMe API directly. | ||
| * Used for testConnection() — the only operation that bypasses the gateway. | ||
| */ | ||
| export async function getMe(botToken: string): Promise<TelegramGetMeResponse> { | ||
| const resp = await fetch(`${TELEGRAM_API_BASE}/bot${botToken}/getMe`, { | ||
| method: 'POST', | ||
| signal: AbortSignal.timeout(DELIVERY_TIMEOUT_MS), | ||
| }); | ||
|
|
||
| if (!resp.ok) { | ||
| throw new TelegramApiError( | ||
| resp.status, | ||
| `Telegram getMe failed with status ${resp.status}`, | ||
| ); | ||
| } | ||
|
|
||
| return resp.json() as Promise<TelegramGetMeResponse>; | ||
| } | ||
|
|
||
| /** | ||
| * Send a text message to a Telegram chat via the gateway's deliver endpoint. | ||
| */ | ||
| export async function sendMessage( | ||
| gatewayUrl: string, | ||
| bearerToken: string, | ||
| chatId: string, | ||
| text: string, | ||
| ): Promise<void> { | ||
| await deliverToGateway(gatewayUrl, bearerToken, { chatId, text }); | ||
| } | ||
|
|
||
| /** | ||
| * Send a message with attachments to a Telegram chat via the gateway. | ||
| */ | ||
| export async function sendMessageWithAttachments( | ||
| gatewayUrl: string, | ||
| bearerToken: string, | ||
| chatId: string, | ||
| text: string | undefined, | ||
| attachmentIds: string[], | ||
| ): Promise<void> { | ||
| await deliverToGateway(gatewayUrl, bearerToken, { | ||
| chatId, | ||
| text, | ||
| attachments: attachmentIds.map((id) => ({ id })), | ||
| }); | ||
| } | ||
|
|
||
| /** Payload accepted by the gateway's /deliver/telegram endpoint. */ | ||
| interface DeliverPayload { | ||
| chatId: string; | ||
| text?: string; | ||
| attachments?: Array<{ id: string }>; | ||
| } | ||
|
|
||
| async function deliverToGateway( | ||
| gatewayUrl: string, | ||
| bearerToken: string, | ||
| payload: DeliverPayload, | ||
| ): Promise<void> { | ||
| const url = `${gatewayUrl}/deliver/telegram`; | ||
| const resp = await fetch(url, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${bearerToken}`, | ||
| }, | ||
| body: JSON.stringify(payload), | ||
| signal: AbortSignal.timeout(DELIVERY_TIMEOUT_MS), | ||
| }); | ||
|
|
||
| if (!resp.ok) { | ||
| const body = await resp.text().catch(() => '<unreadable>'); | ||
| throw new TelegramApiError( | ||
| resp.status, | ||
| `Gateway /deliver/telegram failed (${resp.status}): ${body}`, | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| /** Telegram Bot API types used by the messaging provider. */ | ||
|
|
||
| export interface TelegramUser { | ||
| id: number; | ||
| is_bot: boolean; | ||
| first_name: string; | ||
| last_name?: string; | ||
| username?: string; | ||
| } | ||
|
|
||
| export interface TelegramGetMeResponse { | ||
| ok: boolean; | ||
| result?: TelegramUser; | ||
| description?: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ export function getMessagingProvider(id: string): MessagingProvider { | |
| /** Return all registered providers that have stored credentials. */ | ||
| export function getConnectedProviders(): MessagingProvider[] { | ||
| return Array.from(providers.values()).filter((p) => { | ||
| if (p.isConnected) return p.isConnected(); | ||
|
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.
By short-circuiting on Useful? React with 👍 / 👎. |
||
| const token = getSecureKey(`credential:${p.credentialService}:access_token`); | ||
| return token !== undefined; | ||
| }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Telegram provider unusable via messaging tools:
withProviderTokenthrows because noaccess_tokenexistsAll messaging skill tools (send, reply, auth-test, list, search, read, etc.) call
withProviderToken(provider, fn)which delegates towithValidToken(provider.credentialService, fn). For the Telegram provider,credentialServiceis'telegram', sowithValidTokenlooks forcredential:telegram:access_tokenatassistant/src/security/token-manager.ts:110. However, Telegram stores its credential ascredential:telegram:bot_token(seeadapter.ts:47). Since noaccess_tokenkey exists,withValidTokenthrows aTokenExpiredErrorat line 116 before the callback is ever invoked.Root Cause and Impact
The flow is:
messaging-send.ts:20callswithProviderToken(provider, async (token) => { ... })shared.ts:44-45callswithValidToken(provider.credentialService, fn)→withValidToken('telegram', fn)token-manager.ts:110doesgetSecureKey('credential:telegram:access_token')→ returnsundefinedtoken-manager.ts:116throwsTokenExpiredError('No access token found for "telegram"')The Telegram adapter's
sendMessage(which correctly uses the gateway bearer token, not an OAuth token) is never reached. This makes the Telegram provider completely non-functional through the standard messaging tool interface, despite being registered and appearing as "connected" viaisConnected().Impact: Every attempt to send a Telegram message via the messaging tools will fail with a misleading "No access token found" error, even when the bot token is properly configured.
Prompt for agents
Was this helpful? React with 👍 or 👎 to provide feedback.