larp-quant-agent/worker/telegram.ts
zefiroff f3b533b032
All checks were successful
deploy / deploy (push) Successful in 7s
Quant Agent — multi-agent quant terminal on Hyperliquid
Live HL data → in-browser TA → 4-agent committee → LONG/SHORT/SKIP verdict.
Terminal grid command bar + interactive agent-workflow graph. Paper-trading
worker reuses the browser brain (dry-run + Telegram). Forgejo static deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 19:00:13 +04:00

66 lines
2.3 KiB
TypeScript

/**
* Telegram — the real L6 "Comms" layer, in ~60 lines via the raw Bot API (no
* dependency). Outbound: tgSend() broadcasts signals/closes. Inbound: tgPoll()
* long-polls getUpdates for slash commands (/status, /pnl, /positions, /help).
*
* If no token/chat is configured the worker still runs — messages print to stdout
* so you can demo the whole loop with zero Telegram setup.
*/
import { CONFIG } from './config.ts'
const api = (method: string) => `https://api.telegram.org/bot${CONFIG.tgToken}/${method}`
export const TG_ENABLED = Boolean(CONFIG.tgToken && CONFIG.tgChat)
const stripHtml = (s: string) => s.replace(/<[^>]+>/g, '')
/** Send a message (HTML). Defaults to the configured chat; pass chatId to reply. */
export async function tgSend(text: string, chatId: string | number = CONFIG.tgChat): Promise<void> {
if (!CONFIG.tgToken || !chatId) {
console.log('\n' + stripHtml(text) + '\n')
return
}
try {
const res = await fetch(api('sendMessage'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: chatId,
text,
parse_mode: 'HTML',
disable_web_page_preview: true,
}),
})
if (!res.ok) console.error('[tg] send failed', res.status, await res.text())
} catch (e) {
console.error('[tg] send error', (e as Error).message)
}
}
let offset = 0
/** Poll once for new commands and dispatch them. Cheap to call on an interval. */
export async function tgPoll(
handler: (cmd: string, chatId: number) => Promise<void>,
): Promise<void> {
if (!CONFIG.tgToken) return
try {
const res = await fetch(api('getUpdates') + `?timeout=0&offset=${offset}`)
if (!res.ok) return
const json = (await res.json()) as {
result?: { update_id: number; message?: { text?: string; chat?: { id: number } } }[]
}
for (const u of json.result ?? []) {
offset = u.update_id + 1
const text = u.message?.text
const chatId = u.message?.chat?.id
if (text && chatId != null && text.startsWith('/')) {
// strip @BotName suffix and arguments → bare command
const cmd = text.trim().split(/\s+/)[0].split('@')[0].toLowerCase()
await handler(cmd, chatId)
}
}
} catch {
/* transient — ignore, next poll retries */
}
}