Loading...
Loading...
Operate the joelclaw Telegram channel — primary mobile interface between Joel and the gateway. Covers grammy Bot API, text/media/reactions, inline buttons, callbacks, and streaming.
npx skill4agent add joelhooks/joelclaw telegramJoel (Telegram app)
→ Bot API (long polling via grammy)
→ telegram.ts channel adapter
→ enrichPromptWithVaultContext()
→ command-queue → pi session
→ outbound router → telegram.ts send → Bot API → Joelpackages/gateway/src/channels/telegram.tspackages/gateway/src/telegram-stream.tspackages/gateway/src/outbound/router.tspackages/gateway/src/channels/types.tsChannelgrammy@1.40.0getBot()joelclaw:gateway:telegram:poll-owner:<tokenHash>joelclaw:gateway:telegram:poll-status:<tokenHash>getUpdatestelegram.channel.start_failedconflicttelegram.channel.retry_scheduledtelegram.channel.polling_recovered// Via channel adapter
await telegramChannel.send("telegram:7718912466", "Hello", { format: "html" });
// Direct grammy API (from telegram-stream or daemon)
const bot = getBot();
await bot.api.sendMessage(chatId, text, { parse_mode: "HTML" });TelegramConverter.chunk()chunkMessage()TelegramConverter.convert()InlineButton[][]inline_keyboard// grammy API
await bot.api.setMessageReaction(chatId, messageId, [
{ type: "emoji", emoji: "👍" }
]);<<react:EMOJI>>setMessageReaction// grammy API — reply to a specific message
await bot.api.sendMessage(chatId, text, {
reply_parameters: { message_id: targetMessageId }
});RichSendOptions.replyTo<<reply:MSG_ID>>// Send
await telegramChannel.sendMedia(chatId, "/path/to/file.jpg", { caption: "Look at this" });
// Receive — handled by bot.on("message:photo") etc.
// Downloads via Bot API getFile → local /tmp/joelclaw-media/
// Emits media/received Inngest event for pipeline processingimport { begin, pushDelta, finish, abort } from "./telegram-stream";
// On prompt dispatch
begin({ chatId, bot, replyTo });
// On each text_delta event
pushDelta(delta);
// On message_end
await finish(fullText);finish()parse_mode: "HTML" ▌initialSendPromisefinish()// Send message with buttons
await sendTelegramMessage(chatId, "Choose:", {
buttons: [
[{ text: "✅ Approve", action: "approve:item123" }],
[{ text: "❌ Reject", action: "reject:item123" }],
]
});
// Callback handler fires telegram/callback.received Inngest event
// Then edits message to show action taken + removes buttonsaction:context/stop/esc/stop/kill~/.joelclaw/channels.toml| Env Var | Purpose |
|---|---|
| Grammy bot token |
| Joel's Telegram user ID (only authorized user) |
TELEGRAM_USER_IDchannels.tomlagent-secrets/tmp/joelclaw-media/cat /tmp/joelclaw/gateway.pid && ps aux | grep daemon.tsgrep "telegram.*started" /tmp/joelclaw/gateway.logcurl https://api.telegram.org/bot<TOKEN>/getMerg "telegram.channel.start_failed|failed to start polling|getUpdates" /tmp/joelclaw/gateway.errjoelclaw otel search "telegram.channel.poll_owner" --hours 1joelclaw otel search "telegram.channel.retry_scheduled" --hours 1getUpdatesgrep "command-queue\|enqueue" /tmp/joelclaw/gateway.log | tail -10grep "session\|prompt" /tmp/joelclaw/gateway.log | tail -10grep "outbound\|response ready" /tmp/joelclaw/gateway.log | tail -10text_deltagrep "text_delta" /tmp/joelclaw/gateway.log | tail -5telegram-streamgrep "telegram-stream" /tmp/joelclaw/gateway.log | tail -10initialSendPromisefinish()TelegramConverter.convert(text).validate(result)finish()