@wrongstack/telegram 0.295.0 → 0.295.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bot-queue.d.ts.map +1 -1
- package/dist/bot.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -30
- package/dist/index.js.map +2 -2
- package/dist/outbound-queue.d.ts.map +1 -1
- package/dist/poll-lock.d.ts.map +1 -1
- package/dist/rate-limiter.d.ts +7 -0
- package/dist/rate-limiter.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts", "../src/api-client.ts", "../src/bot.ts", "../src/config.ts", "../src/redact.ts", "../src/format.ts", "../src/poll-lock.ts", "../src/offset-store.ts", "../src/security/outbound.ts", "../src/slash-commands/index.ts", "../src/tools/telegram-approve.ts", "../src/outbound-queue.ts", "../src/rate-limiter.ts", "../src/bot-queue.ts", "../src/notification-channel.ts", "../src/tools/telegram-read.ts", "../src/tools/telegram-send.ts", "../src/config-classifier.ts"],
|
|
4
|
-
"sourcesContent": ["import type { PluginAPI } from '@wrongstack/core/plugin';\nimport type { Config, Logger, Plugin, SlashCommand } from '@wrongstack/core/types';\nimport { expectDefined } from '@wrongstack/core/utils';\nimport type { TelegramIncomingMessage } from './bot.js';\nimport { TelegramBot } from './bot.js';\nimport {\n DEFAULT_CONFIG,\n PLUGIN_CONFIG_ALIASES,\n PLUGIN_NAME,\n readTelegramConfig,\n readTelegramConfigFromConfig,\n TELEGRAM_CONFIG_FIELDS,\n telegramConfigSchema,\n} from './config.js';\nimport type { SessionEndedLike, ToolExecutedLike } from './format.js';\nimport { formatDelegateCompleted, formatSessionEnded, formatToolExecuted } from './format.js';\nimport { lockPathForToken, PollLock } from './poll-lock.js';\nimport { OffsetStore } from './offset-store.js';\nimport { scrubTelegramOutboundText } from './security/outbound.js';\nimport { tgChatIdCommand, tgHealthCommand, tgSendCommand } from './slash-commands/index.js';\nimport { makeTelegramApproveTool } from './tools/telegram-approve.js';\nimport { TelegramBotOutbound } from './bot-queue.js';\nimport { TelegramNotificationChannel } from './notification-channel.js';\nimport { makeTelegramReadTool } from './tools/telegram-read.js';\nimport { makeTelegramSendTool } from './tools/telegram-send.js';\nimport { diffConfigKeys } from './config-classifier.js';\n\n// ---------------------------------------------------------------------------\n// Teardown state\n// ---------------------------------------------------------------------------\n\n/** Mutable runtime config \u2014 updated via api.onConfigChange so changes take\n * effect without restarting the plugin. */\ninterface RuntimeConfig {\n notifyChatId: string | number | undefined;\n allowedOutboundChats: Array<string | number>;\n allowedUserIds: Array<string | number>;\n allowGroupApprovals: boolean;\n notifyOnSessionEnd: boolean;\n notifyOnDelegate: boolean;\n longToolThresholdMs: number;\n maxMessageLength: number;\n outboundQueuePerChat: number;\n outboundQueueConcurrency: number;\n /** Per-chat rate-limit pacing for outbound sends (live via getter). */\n rateLimitTokensPerSecond: number;\n /** Per-chat rate-limit burst size for outbound sends (live via getter). */\n rateLimitBurst: number;\n /** Telegram parse mode resolved per-send so hot-reload takes effect live. */\n parseMode: '' | 'HTML' | 'MarkdownV2';\n}\n\ninterface RuntimeState {\n bot: TelegramBot;\n outbound: TelegramBotOutbound;\n cleanups: Array<() => void>;\n}\n\nlet teardownState: RuntimeState | null = null;\n\nconst DENY_ALL_INBOUND = '__wrongstack_telegram_inbound_disabled__';\n\nfunction inboundAllowlist(cfg: ReturnType<typeof readTelegramConfig>): {\n allowedUsers: Set<string>;\n allowedChats: Set<string>;\n} {\n if (cfg.inboundMode === 'public') {\n return { allowedUsers: new Set(), allowedChats: new Set() };\n }\n if (cfg.inboundMode === 'paired') {\n const pairedUsers = new Set((cfg.allowedUsers ?? []).map(String));\n return {\n allowedUsers:\n pairedUsers.size > 0 ? pairedUsers : new Set([String(expectDefined(cfg.notifyChatId))]),\n allowedChats: new Set([String(expectDefined(cfg.notifyChatId))]),\n };\n }\n if (cfg.inboundMode === 'allowlist') {\n return {\n allowedUsers: new Set((cfg.allowedUsers ?? []).map(String)),\n allowedChats: new Set((cfg.allowedChats ?? []).map(String)),\n };\n }\n return {\n allowedUsers: new Set([DENY_ALL_INBOUND]),\n allowedChats: new Set([DENY_ALL_INBOUND]),\n };\n}\n\nfunction runCleanups(cleanups: Array<() => void>, log: Logger): void {\n while (cleanups.length > 0) {\n const cleanup = cleanups.pop();\n try {\n cleanup?.();\n } catch (err) {\n log.debug(`Telegram cleanup failed: ${(err as Error).message}`);\n }\n }\n}\n\nfunction disposeRuntime(log: Logger): void {\n const state = teardownState;\n teardownState = null;\n if (state) runCleanups(state.cleanups, log);\n}\n\nfunction registerCommand(api: PluginAPI, command: SlashCommand, cleanups: Array<() => void>): void {\n api.slashCommands.register(command);\n cleanups.push(() => {\n api.slashCommands.unregister(`${PLUGIN_NAME}:${command.name}`);\n });\n}\n\n/**\n * Build the plugin's `defaultConfig` from `DEFAULT_CONFIG` with defensive\n * deep copies of every field (arrays and any future nested values).\n * Using `structuredClone` ensures any key added to `DEFAULT_CONFIG` is\n * automatically copied rather than shared by reference, preventing silent\n * drift from the documented defaults. Scalar (immutable) values are\n * unaffected by deep copy.\n *\n * Return type widens to `Record<string, unknown>` to align with the\n * `Plugin.defaultConfig` interface (see `packages/core/src/types/plugin.ts`).\n * The narrower `DEFAULT_CONFIG` shape is still enforced by\n * `telegramConfigSchema` validation downstream.\n */\nfunction telegramDefaultConfig(): Record<string, unknown> {\n return structuredClone(DEFAULT_CONFIG) as Record<string, unknown>;\n}\n\n/** Read the Telegram section from a full Config object.\n * Delegates to {@link readTelegramConfigFromConfig} for the canonical\n * extension-slice extraction + default merge, then applies the runtime\n * coercions that `RuntimeConfig` requires (String chatId, array copies). */\nfunction telegramFromConfig(cfg: Config): {\n notifyChatId: string | number | undefined;\n allowedOutboundChats: Array<string | number>;\n allowedUserIds: Array<string | number>;\n allowGroupApprovals: boolean;\n notifyOnSessionEnd: boolean;\n notifyOnDelegate: boolean;\n longToolThresholdMs: number;\n maxMessageLength: number;\n outboundQueuePerChat: number;\n outboundQueueConcurrency: number;\n rateLimitTokensPerSecond: number;\n rateLimitBurst: number;\n parseMode: '' | 'HTML' | 'MarkdownV2';\n} {\n const tg = readTelegramConfigFromConfig(cfg);\n return {\n notifyChatId: tg.notifyChatId !== undefined ? String(tg.notifyChatId) : undefined,\n allowedOutboundChats: [...(tg.allowedOutboundChats ?? [])],\n allowedUserIds: [...(tg.allowedUsers ?? [])],\n allowGroupApprovals: tg.allowGroupApprovals ?? false,\n notifyOnSessionEnd: tg.notifyOnSessionEnd ?? false,\n notifyOnDelegate: tg.notifyOnDelegate ?? true,\n longToolThresholdMs: tg.longToolThresholdMs ?? 30_000,\n maxMessageLength: tg.maxMessageLength ?? 4000,\n outboundQueuePerChat: tg.outboundQueuePerChat ?? 32,\n outboundQueueConcurrency: tg.outboundQueueConcurrency ?? 4,\n rateLimitTokensPerSecond: tg.rateLimitTokensPerSecond ?? 0.33,\n rateLimitBurst: tg.rateLimitBurst ?? 1,\n parseMode: tg.parseMode ?? '',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Plugin\n// ---------------------------------------------------------------------------\n\nconst plugin: Plugin = {\n name: PLUGIN_NAME,\n version: '0.3.4',\n description: 'Telegram bridge \u2014 send/receive messages, get agent notifications.',\n apiVersion: '^0.1.10',\n capabilities: {\n tools: true,\n slashCommands: true,\n pipelines: [],\n },\n configAliases: [...PLUGIN_CONFIG_ALIASES],\n configFields: TELEGRAM_CONFIG_FIELDS,\n configSchema: telegramConfigSchema,\n defaultConfig: telegramDefaultConfig(),\n\n async setup(api) {\n const log = api.log;\n disposeRuntime(log);\n const cfg = readTelegramConfig(api);\n\n log.info('Starting Telegram plugin...');\n\n // ---- Mutable runtime config (updated via onConfigChange) ----\n const runtimeCfg: RuntimeConfig = {\n notifyChatId: cfg.notifyChatId,\n allowedOutboundChats: [...(cfg.allowedOutboundChats ?? [])],\n allowedUserIds: [...(cfg.allowedUsers ?? [])],\n allowGroupApprovals: cfg.allowGroupApprovals ?? false,\n notifyOnSessionEnd: cfg.notifyOnSessionEnd ?? false,\n notifyOnDelegate: cfg.notifyOnDelegate ?? true,\n longToolThresholdMs: cfg.longToolThresholdMs ?? 30_000,\n maxMessageLength: cfg.maxMessageLength ?? 4000,\n outboundQueuePerChat: cfg.outboundQueuePerChat ?? 32,\n outboundQueueConcurrency: cfg.outboundQueueConcurrency ?? 4,\n rateLimitTokensPerSecond: cfg.rateLimitTokensPerSecond ?? 0.33,\n rateLimitBurst: cfg.rateLimitBurst ?? 1,\n parseMode: cfg.parseMode ?? '',\n };\n\n // ---- Bot ----\n // Telegram allows one getUpdates consumer per token: elect a single\n // poller across wstack instances so concurrent TUI/WebUI/projects don't\n // fight over the token (HTTP 409 on every poll).\n const lock =\n cfg.singleInstanceLock === false\n ? undefined\n : new PollLock(lockPathForToken(cfg.botToken), { log });\n // Persist the polling cursor so a crash/restart doesn't replay messages.\n // Default to a token-scoped store under ~/.wrongstack/telegram; an explicit\n // offsetStoragePath overrides the location. Persistence is disabled only\n // when offsetStoragePath is set to an empty string.\n const offsetStore =\n cfg.offsetStoragePath === ''\n ? undefined\n : new OffsetStore({ token: cfg.botToken, path: cfg.offsetStoragePath });\n const bot = new TelegramBot({\n token: cfg.botToken,\n pollIntervalSec: cfg.pollIntervalSec ?? 2,\n ...inboundAllowlist(cfg),\n bufferSize: 50,\n log,\n offsetStore,\n lock,\n getParseMode: () => runtimeCfg.parseMode,\n onMessage(msg: TelegramIncomingMessage) {\n // Emit custom event so other plugins or the host can react.\n // The TUI can subscribe and surface it (future hook).\n api.emitCustom('telegram:message_received', msg);\n\n // Bridge to the inter-agent mailbox so other agents (leader,\n // background watchers) can discover Telegram messages without\n // polling the bot buffer. Broadcast as a low-priority note so\n // it informs without interrupting. Mailbox delivery is\n // best-effort \u2014 failure must never disrupt the bot's polling\n // loop, but surface it for debugging.\n const mailbox = api.mailbox;\n if (mailbox) {\n mailbox\n .send({\n from: 'telegram',\n to: 'leader',\n type: 'note',\n subject: scrubTelegramOutboundText(\n `\uD83D\uDCE8 Telegram from ${msg.userName ?? `user_${msg.userId ?? 'unknown'}`}`,\n ),\n body: scrubTelegramOutboundText(msg.text),\n priority: 'low',\n })\n .catch((err: unknown) => {\n log.debug(`Telegram\u2192mailbox bridge delivery failed: ${(err as Error).message}`);\n });\n }\n\n // Keep untrusted inbound content in the bot buffer only. Logs expose\n // bounded metadata so message text, sender, and chat IDs cannot leak.\n log.info(`\uD83D\uDCE8 Telegram message received (${Math.min(bot.bufferCount, 50)} unread)`);\n },\n });\n\n // Validate the token before mutating host registries or acquiring the poll\n // lock. A failed preflight must leave setup observationally atomic.\n const probe = await bot.health();\n if (!probe.ok) {\n bot.stop();\n throw new Error(\n `Telegram plugin startup failed: ${probe.error ?? 'unknown error'}. ` +\n `Verify botToken in extensions.telegram (token from @BotFather, format \"<id>:<35+ chars>\").`,\n );\n }\n log.info(`Telegram self-test ok: @${probe.username ?? 'unknown'} (api.telegram.org reachable)`);\n\n const cleanups: Array<() => void> = [];\n try {\n // Bot cleanup is registered first so it runs last, after every host-side\n // listener and registry entry has been detached.\n cleanups.push(() => bot.stop());\n\n // Bounded outbound queue with per-chat backpressure. Notification events\n // and the /telegram:send slash command both enqueue through it; manual\n // telegram_send tool sends go through bot.sendMessage directly so user\n // errors surface immediately. The queue is drained on teardown.\n // Rate-limit fields are passed as getter callbacks so hot-reload\n // (via api.onConfigChange) takes effect on the next send without\n // rebuilding the queue.\n const outbound = new TelegramBotOutbound({\n bot,\n log,\n maxPerChat: runtimeCfg.outboundQueuePerChat,\n maxConcurrency: runtimeCfg.outboundQueueConcurrency,\n getRateLimitTokensPerSecond: () => runtimeCfg.rateLimitTokensPerSecond,\n getRateLimitBurst: () => runtimeCfg.rateLimitBurst,\n });\n cleanups.push(() => {\n void outbound.stop();\n });\n\n // ---- Register tools ----\n const sendTool = makeTelegramSendTool({\n bot,\n getDefaultChatId: () => runtimeCfg.notifyChatId,\n getAllowedOutboundChatIds: () => runtimeCfg.allowedOutboundChats,\n maxMessageLength: runtimeCfg.maxMessageLength,\n log,\n });\n const readTool = makeTelegramReadTool({ bot });\n const approveTool = makeTelegramApproveTool({\n bot,\n getDefaultChatId: () => runtimeCfg.notifyChatId,\n getAllowedOutboundChatIds: () => runtimeCfg.allowedOutboundChats,\n getAllowedUserIds: () => runtimeCfg.allowedUserIds,\n getAllowGroupApprovals: () => runtimeCfg.allowGroupApprovals,\n maxMessageLength: runtimeCfg.maxMessageLength,\n log,\n });\n for (const tool of [sendTool, readTool, approveTool]) {\n api.tools.register(tool);\n cleanups.push(() => {\n api.tools.unregister(tool.name);\n });\n }\n\n // ---- Event subscriptions ----\n\n // System prompts receive metadata only. Message text and identity stay\n // behind the explicit telegram_read tool boundary.\n const unregisterPrompt = api.registerSystemPromptContributor(async () => {\n const unreadCount = Math.min(bot.bufferCount, 50);\n if (unreadCount === 0) return [];\n return [\n {\n type: 'text' as const,\n text: [\n '## Telegram Inbox',\n `You have ${unreadCount} unread Telegram message(s).`,\n 'Use `telegram_read` to retrieve them when needed.',\n ].join('\\n'),\n },\n ];\n });\n cleanups.push(unregisterPrompt);\n\n // Register commands one at a time so a later collision can roll back the\n // commands already installed by this setup attempt.\n for (const command of [\n tgHealthCommand(bot, cfg),\n tgSendCommand(\n bot,\n {\n getDefaultChatId: () => runtimeCfg.notifyChatId,\n getAllowedOutboundChatIds: () => runtimeCfg.allowedOutboundChats,\n getMaxMessageLength: () => runtimeCfg.maxMessageLength,\n },\n outbound,\n ),\n tgChatIdCommand(cfg.notifyChatId),\n ]) {\n registerCommand(api, command, cleanups);\n }\n\n // ---- Notification channel ----\n // The `TelegramNotificationChannel` is registered with the host's\n // `Notifier` (api.notifier) when available, so the central router can\n // route `\"telegram\"`-channel notifications directly. The in-plugin\n // event handlers below still handle the \"when to notify\" logic; they\n // send through the channel directly. Once the central Notifier router\n // is fully built, these handlers move to the router entirely.\n let notifyChannel: TelegramNotificationChannel | undefined;\n if (runtimeCfg.notifyChatId !== undefined) {\n notifyChannel = new TelegramNotificationChannel({\n bot,\n chatId: runtimeCfg.notifyChatId,\n maxMessageLength: runtimeCfg.maxMessageLength,\n enqueueNotification: (chatId, text) => outbound.enqueueNotification(chatId, text),\n log,\n });\n // Register with the host's Notifier so other subsystems can send\n // notifications to the \"telegram\" channel without knowing Telegram.\n api.notifier?.registerChannel(notifyChannel);\n }\n\n // ---- Notification event handlers ----\n // Always subscribed; guard at event time against runtime flags so changes\n // take effect immediately without needing to restart the plugin.\n // Delivery goes through `notifyChannel` for rendering/scrubbing and then\n // through the shared outbound queue for ordering and backpressure.\n\n cleanups.push(\n api.events.on('session.ended', (event) => {\n if (!runtimeCfg.notifyOnSessionEnd || !runtimeCfg.notifyChatId || !notifyChannel) return;\n const payload: SessionEndedLike = {\n id: scrubTelegramOutboundText(event.id),\n inputTokens: event.usage.input,\n outputTokens: event.usage.output,\n cacheRead: event.usage.cacheRead,\n cacheWrite: event.usage.cacheWrite,\n };\n notifyChannel.deliver({\n title: 'Session ended',\n body: formatSessionEnded(payload),\n level: 'info',\n source: 'session.end',\n }).then(r => {\n if (!r.ok) log.warn(`session.ended notification delivery failed: ${r.error ?? 'unknown'}`);\n });\n }),\n );\n\n cleanups.push(\n api.events.on('tool.executed', (event) => {\n if (!runtimeCfg.notifyChatId || !notifyChannel || runtimeCfg.longToolThresholdMs <= 0) return;\n if (event.durationMs < runtimeCfg.longToolThresholdMs) return;\n const payload: ToolExecutedLike = {\n name: event.name,\n ok: event.ok,\n durationMs: event.durationMs,\n output:\n event.output === undefined ? undefined : scrubTelegramOutboundText(event.output),\n };\n notifyChannel.deliver({\n title: event.ok ? 'Tool completed' : 'Tool failed',\n body: formatToolExecuted(payload),\n level: event.ok ? 'info' : 'warning',\n source: 'tool.exec',\n }).then(r => {\n if (!r.ok) log.warn(`tool.executed notification delivery failed: ${r.error ?? 'unknown'}`);\n });\n }),\n );\n\n cleanups.push(\n api.events.on('delegate.completed', (event) => {\n if (!runtimeCfg.notifyOnDelegate || !runtimeCfg.notifyChatId || !notifyChannel) return;\n const safeEvent = {\n ...event,\n target: scrubTelegramOutboundText(event.target),\n task: scrubTelegramOutboundText(event.task),\n status:\n event.status === undefined ? undefined : scrubTelegramOutboundText(event.status),\n summary: scrubTelegramOutboundText(event.summary),\n };\n notifyChannel.deliver({\n title: `Delegate: ${safeEvent.target}`,\n body: formatDelegateCompleted(safeEvent),\n level: event.ok ? 'info' : 'warning',\n source: 'delegate.completed',\n }).then(r => {\n if (!r.ok) log.warn(`delegate.completed notification delivery failed: ${r.error ?? 'unknown'}`);\n });\n }),\n );\n\n // ---- Live config updates (P2.3 \u2014 atomic reconfiguration) ----\n // api.config is frozen at setup, but onConfigChange fires whenever the\n // ConfigStore is updated (from CLI /settings, WebUI prefSync, /telegram-settings).\n //\n // P2.2 classifies each changed key as HOT (apply live) or RESTART\n // (requires bot rebuild). HOT keys mutate the shared runtimeCfg so\n // every handler picks up the new value on the next event. RESTART\n // keys are logged with a restart hint \u2014 the operator must restart the\n // plugin for those to take effect. A future iteration will attempt\n // an atomic bot rebuild for restart keys (build \u2192 health-check \u2192\n // swap \u2192 rollback on failure).\n const unlistenConfig = api.onConfigChange((next, prev) => {\n // Build full TelegramPluginConfig snapshots for the P2.2 classifier.\n const nextTg = readTelegramConfigFromConfig(next);\n const prevTg = readTelegramConfigFromConfig(prev);\n const changedKeys = diffConfigKeys(prevTg, nextTg);\n const hotKeys = changedKeys.filter(c => c.classification === 'hot').map(c => c.key);\n const restartKeys = changedKeys.filter(c => c.classification === 'restart-required').map(c => c.key);\n\n // Phase 1: Apply hot keys to the shared runtime config.\n // Only keys classified as hot-reload-safe are applied live.\n // Restart-required keys (notifyChatId, outboundQueue*, singleInstanceLock,\n // botToken, offsetStoragePath) keep their previous values until\n // the operator restarts the plugin.\n const fresh = telegramFromConfig(next);\n const was = telegramFromConfig(prev);\n const hotSet = new Set<string>(hotKeys);\n\n // Table-driven hot-apply. Each entry maps a Telegram config key\n // to the runtime mutation to perform when that key changed.\n // Adding a new hot key here is a one-line change instead of a\n // duplicated `if (hotSet.has(...))` arm.\n type HotApplier = (runtimeCfg: RuntimeConfig, fresh: ReturnType<typeof telegramFromConfig>) => void;\n const HOT_APPLIERS: ReadonlyMap<string, HotApplier> = new Map<string, HotApplier>([\n ['allowedOutboundChats', (r, f) => { r.allowedOutboundChats = f.allowedOutboundChats; }],\n ['allowedUsers', (r, f) => { r.allowedUserIds = f.allowedUserIds; }],\n ['notifyOnSessionEnd', (r, f) => { r.notifyOnSessionEnd = f.notifyOnSessionEnd; }],\n ['notifyOnDelegate', (r, f) => { r.notifyOnDelegate = f.notifyOnDelegate; }],\n ['longToolThresholdMs', (r, f) => { r.longToolThresholdMs = f.longToolThresholdMs; }],\n ['maxMessageLength', (r, f) => { r.maxMessageLength = f.maxMessageLength; }],\n ['allowGroupApprovals', (r, f) => { r.allowGroupApprovals = f.allowGroupApprovals; }],\n ['rateLimitTokensPerSecond', (r, f) => { r.rateLimitTokensPerSecond = f.rateLimitTokensPerSecond; }],\n ['rateLimitBurst', (r, f) => { r.rateLimitBurst = f.rateLimitBurst; }],\n ['parseMode', (r, f) => { r.parseMode = f.parseMode; }],\n ]);\n for (const [key, apply] of HOT_APPLIERS) {\n if (hotSet.has(key)) apply(runtimeCfg, fresh);\n }\n // notifyChatId is RESTART-REQUIRED: the inbound allowlist\n // (built at setup) still uses the old value. Applying the new\n // one here would make outbound notifications target a chat the\n // inbound allowlist does not yet recognise. Deferred to restart.\n\n // When maxMessageLength changes (hot), rebuild the notification\n // channel so it picks up the new limit. notifyChatId changes\n // are deferred to restart, so the channel keeps the current\n // (old) chat ID.\n if (hotSet.has('maxMessageLength') && fresh.maxMessageLength !== was.maxMessageLength) {\n notifyChannel =\n runtimeCfg.notifyChatId !== undefined\n ? new TelegramNotificationChannel({\n bot,\n chatId: runtimeCfg.notifyChatId,\n maxMessageLength: fresh.maxMessageLength,\n enqueueNotification: (chatId, text) =>\n outbound.enqueueNotification(chatId, text),\n log,\n })\n : undefined;\n }\n\n // Phase 2: Surface restart-required keys.\n if (restartKeys.length > 0) {\n log.warn(\n 'Telegram config changed restart-required keys \u2014 restart the plugin for these to take effect',\n { restartKeys, hotKeys },\n );\n api.emitCustom('telegram:restart_required', {\n keys: restartKeys,\n message: `Restart required for: ${restartKeys.join(', ')}`,\n });\n }\n\n log.debug('Telegram config updated', {\n hotApplied: hotKeys,\n restartRequired: restartKeys,\n notifyOnSessionEnd: runtimeCfg.notifyOnSessionEnd,\n notifyOnDelegate: runtimeCfg.notifyOnDelegate,\n longToolThresholdMs: runtimeCfg.longToolThresholdMs,\n parseMode: runtimeCfg.parseMode,\n notifyChatId: runtimeCfg.notifyChatId ?? 'not set',\n });\n });\n cleanups.push(unlistenConfig);\n\n // Polling is the final side effect: it may acquire the cross-process\n // lock and create timers, and bot.stop() releases all of them.\n bot.start();\n teardownState = { bot, outbound, cleanups };\n log.info('Telegram plugin ready');\n } catch (err) {\n teardownState = null;\n runCleanups(cleanups, log);\n throw err;\n }\n },\n\n async teardown(api) {\n const hadRuntime = teardownState !== null;\n disposeRuntime(api.log);\n if (hadRuntime) api.log.info('Telegram plugin torn down');\n },\n\n async health() {\n const state = teardownState;\n if (!state?.bot) return { ok: false, message: 'Plugin not initialized' };\n const h = await state.bot.health();\n return h;\n },\n};\n\nexport default plugin;\n\n// Exposed for tests to inspect the queue without going through the API surface.\nexport { teardownState };\n\n// Re-export the types and classes consumers may want\nexport type { TelegramIncomingMessage } from './bot.js';\nexport type { TelegramPluginConfig } from './config.js';\nexport { TelegramNotificationChannel } from './notification-channel.js';\nexport type { TelegramNotificationChannelOptions } from './notification-channel.js';\n", "// ---------------------------------------------------------------------------\n// Telegram Bot API models used by the plugin\n// ---------------------------------------------------------------------------\n\nexport interface TelegramApiUser {\n id: number;\n is_bot: boolean;\n first_name: string;\n username?: string | undefined;\n}\n\nexport type TelegramApiChatType = 'private' | 'group' | 'supergroup' | 'channel';\n\nexport interface TelegramApiChat {\n id: number;\n type: TelegramApiChatType;\n title?: string | undefined;\n username?: string | undefined;\n}\n\nexport interface TelegramApiMessage {\n message_id: number;\n from?: TelegramApiUser | undefined;\n chat: TelegramApiChat;\n date: number;\n text?: string | undefined;\n}\n\nexport interface TelegramApiCallbackQuery {\n id: string;\n from?: TelegramApiUser | undefined;\n message?: { message_id: number; chat: TelegramApiChat } | undefined;\n data?: string | undefined;\n}\n\nexport interface TelegramApiUpdate {\n update_id: number;\n message?: TelegramApiMessage | undefined;\n edited_message?: TelegramApiMessage | undefined;\n callback_query?: TelegramApiCallbackQuery | undefined;\n}\n\nexport interface TelegramInlineKeyboardButton {\n text: string;\n callback_data: string;\n}\n\ninterface TelegramApiEnvelope<T> {\n ok: boolean;\n result?: T | undefined;\n description?: string | undefined;\n error_code?: number | undefined;\n parameters?:\n | {\n retry_after?: number | undefined;\n migrate_to_chat_id?: number | undefined;\n }\n | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Structured, token-safe failure model\n// ---------------------------------------------------------------------------\n\nexport type TelegramApiClientErrorKind = 'network' | 'http' | 'parse' | 'api';\n\nexport abstract class TelegramApiClientError extends Error {\n readonly kind: TelegramApiClientErrorKind;\n readonly method: string;\n\n protected constructor(kind: TelegramApiClientErrorKind, method: string, message: string) {\n super(message);\n this.kind = kind;\n this.method = method;\n }\n}\n\nexport class TelegramNetworkError extends TelegramApiClientError {\n readonly detail: string;\n readonly aborted: boolean;\n\n constructor(method: string, detail: string, aborted = false) {\n super('network', method, `Telegram network error during ${method}: ${detail}`);\n this.name = 'TelegramNetworkError';\n this.detail = detail;\n this.aborted = aborted;\n }\n}\n\nexport class TelegramHttpError extends TelegramApiClientError {\n readonly status: number;\n\n constructor(method: string, status: number, statusText?: string | undefined) {\n const suffix = statusText ? ` ${statusText}` : '';\n super('http', method, `Telegram HTTP error during ${method}: ${status}${suffix}`);\n this.name = 'TelegramHttpError';\n this.status = status;\n }\n}\n\nexport class TelegramResponseParseError extends TelegramApiClientError {\n constructor(method: string, detail: string) {\n super('parse', method, `Telegram response parse error during ${method}: ${detail}`);\n this.name = 'TelegramResponseParseError';\n }\n}\n\nexport class TelegramBotApiError extends TelegramApiClientError {\n readonly errorCode?: number | undefined;\n readonly httpStatus?: number | undefined;\n readonly description: string;\n readonly retryAfterSeconds?: number | undefined;\n readonly migrateToChatId?: number | undefined;\n\n constructor(\n method: string,\n opts: {\n errorCode?: number | undefined;\n httpStatus?: number | undefined;\n description: string;\n retryAfterSeconds?: number | undefined;\n migrateToChatId?: number | undefined;\n },\n ) {\n const code = opts.errorCode === undefined ? 'unknown' : String(opts.errorCode);\n super('api', method, `Telegram API error ${code} during ${method}: ${opts.description}`);\n this.name = 'TelegramBotApiError';\n this.errorCode = opts.errorCode;\n this.httpStatus = opts.httpStatus;\n this.description = opts.description;\n this.retryAfterSeconds = opts.retryAfterSeconds;\n this.migrateToChatId = opts.migrateToChatId;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Retry and backoff policy\n// ---------------------------------------------------------------------------\n\nexport interface RetryDecision {\n /** Whether to retry the request. */\n retry: boolean;\n /** Milliseconds to wait before retrying. 0 when retry is false. */\n delayMs: number;\n}\n\n/** Base delay for exponential backoff (1 s). */\nconst BACKOFF_BASE_MS = 1_000;\n/** Maximum delay cap (30 s). */\nconst BACKOFF_MAX_MS = 30_000;\n\n/**\n * Classify a caught error and decide whether to retry, and how long to wait.\n * @param err The error thrown by api-client methods.\n * @param attempt 1-based attempt counter.\n * @returns A RetryDecision.\n */\nexport function classifyRetry(err: unknown, attempt: number): RetryDecision {\n if (attempt >= 3) return { retry: false, delayMs: 0 };\n\n if (err instanceof TelegramHttpError) {\n if (err.status === 429 || err.status === 409 || err.status >= 500) {\n const delayMs = Math.min(\n Math.ceil(BACKOFF_BASE_MS * 2 ** (attempt - 1) * (1 + Math.random() * 0.2)),\n BACKOFF_MAX_MS,\n );\n return { retry: true, delayMs };\n }\n return { retry: false, delayMs: 0 };\n }\n if (err instanceof TelegramResponseParseError) return { retry: false, delayMs: 0 };\n if (err instanceof TelegramNetworkError && err.aborted) return { retry: false, delayMs: 0 };\n\n if (err instanceof TelegramBotApiError) {\n const code = err.errorCode;\n if (code !== undefined && code >= 400 && code < 500 && code !== 429 && code !== 409) {\n return { retry: false, delayMs: 0 };\n }\n if (code === 429) {\n const baseDelay =\n err.retryAfterSeconds !== undefined\n ? err.retryAfterSeconds * 1000\n : BACKOFF_BASE_MS * 2 ** (attempt - 1);\n const delayMs = Math.min(Math.ceil(baseDelay * (1 + Math.random() * 0.3)), BACKOFF_MAX_MS);\n return { retry: true, delayMs };\n }\n if (code === 409) {\n const delayMs = Math.min(BACKOFF_BASE_MS * 2 ** (attempt - 1), BACKOFF_MAX_MS);\n return { retry: true, delayMs };\n }\n if (code !== undefined && code >= 500) {\n const delayMs = Math.min(\n Math.ceil(BACKOFF_BASE_MS * 2 ** (attempt - 1) * (1 + Math.random() * 0.2)),\n BACKOFF_MAX_MS,\n );\n return { retry: true, delayMs };\n }\n if (code === undefined) {\n const delayMs = Math.min(BACKOFF_BASE_MS * 2 ** (attempt - 1), BACKOFF_MAX_MS);\n return { retry: true, delayMs };\n }\n }\n\n const delayMs = Math.min(\n Math.ceil(BACKOFF_BASE_MS * 2 ** (attempt - 1) * (1 + Math.random() * 0.3)),\n BACKOFF_MAX_MS,\n );\n return { retry: true, delayMs };\n}\n\n// ---------------------------------------------------------------------------\n// Typed transport\n// ---------------------------------------------------------------------------\n\ntype TelegramFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\n\nexport interface TelegramApiClientOptions {\n token: string;\n /** Override used by deterministic tests or Bot API proxies. */\n apiRoot?: string | undefined;\n /** Optional transport injection. Defaults to globalThis.fetch at call time. */\n fetch?: TelegramFetch | undefined;\n}\n\nexport interface TelegramRequestOptions {\n signal?: AbortSignal | undefined;\n /** Optional parse_mode override for this call. */\n parseMode?: '' | 'HTML' | 'MarkdownV2' | undefined;\n}\n\nexport interface TelegramGetUpdatesOptions extends TelegramRequestOptions {\n deadlineMs?: number | undefined;\n\n offset: number;\n timeoutSeconds: number;\n}\n\n/** Build the one canonical, token-bearing Bot API base URL. Never log this value. */\nexport function buildTelegramBotApiBaseUrl(\n token: string,\n apiRoot = 'https://api.telegram.org',\n): string {\n return `${apiRoot.replace(/\\/+$/, '')}/bot${token}`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nexport function abortableSleep(ms: number, signal?: AbortSignal | undefined): Promise<void> {\n if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));\n return new Promise((resolve, reject) => {\n if (signal.aborted) {\n reject(new DOMException('The operation was aborted', 'AbortError'));\n return;\n }\n\n const cleanup = () => signal.removeEventListener('abort', onAbort);\n const timer = setTimeout(() => {\n cleanup();\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n cleanup();\n reject(new DOMException('The operation was aborted', 'AbortError'));\n };\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nfunction errorDetail(error: unknown): string {\n if (error instanceof Error) return error.message;\n return String(error);\n}\n\n/** Compose optional parent signal + deadline into one AbortSignal. Returns undefined when neither is set. */\nfunction composedSignal(\n signal?: AbortSignal | undefined,\n deadlineMs?: number | undefined,\n): AbortSignal | undefined {\n if (deadlineMs !== undefined && signal) {\n return AbortSignal.any([signal, AbortSignal.timeout(deadlineMs)]);\n }\n if (deadlineMs !== undefined) return AbortSignal.timeout(deadlineMs);\n return signal;\n}\n\nexport class TelegramApiClient {\n readonly safeBaseUrl: string;\n\n private readonly token: string;\n private readonly baseUrl: string;\n private readonly fetchOverride?: TelegramFetch | undefined;\n\n constructor(opts: TelegramApiClientOptions) {\n this.token = opts.token;\n this.baseUrl = buildTelegramBotApiBaseUrl(opts.token, opts.apiRoot);\n this.safeBaseUrl = this.redact(this.baseUrl);\n this.fetchOverride = opts.fetch;\n }\n\n getMe(opts?: TelegramRequestOptions): Promise<TelegramApiUser> {\n return this.request<TelegramApiUser>('getMe', { signal: composedSignal(opts?.signal) });\n }\n\n getUpdates(opts: TelegramGetUpdatesOptions): Promise<TelegramApiUpdate[]> {\n const query = new URLSearchParams({\n offset: String(opts.offset),\n timeout: String(opts.timeoutSeconds),\n });\n return this.request<TelegramApiUpdate[]>('getUpdates', {\n query,\n signal: composedSignal(opts.signal, opts.deadlineMs),\n });\n }\n\n sendMessage(\n chatId: string | number,\n text: string,\n opts?: TelegramRequestOptions,\n ): Promise<TelegramApiMessage> {\n const body: Record<string, unknown> = {\n chat_id: String(chatId),\n text,\n disable_web_page_preview: true,\n };\n if (opts?.parseMode) body.parse_mode = opts.parseMode;\n return this.request<TelegramApiMessage>('sendMessage', {\n body,\n signal: composedSignal(opts?.signal),\n });\n }\n\n sendMessageWithKeyboard(\n chatId: string | number,\n text: string,\n buttons: readonly TelegramInlineKeyboardButton[],\n opts?: TelegramRequestOptions,\n ): Promise<TelegramApiMessage> {\n const body: Record<string, unknown> = {\n chat_id: String(chatId),\n text,\n disable_web_page_preview: true,\n reply_markup: {\n inline_keyboard: [\n buttons.map((button) => ({\n text: button.text,\n callback_data: button.callback_data,\n })),\n ],\n },\n };\n if (opts?.parseMode) body.parse_mode = opts.parseMode;\n return this.request<TelegramApiMessage>('sendMessage', {\n body,\n signal: composedSignal(opts?.signal),\n });\n }\n\n answerCallbackQuery(\n callbackQueryId: string,\n text: string,\n showAlert: boolean,\n opts?: TelegramRequestOptions,\n ): Promise<boolean> {\n return this.request<boolean>('answerCallbackQuery', {\n body: {\n callback_query_id: callbackQueryId,\n text,\n show_alert: showAlert,\n },\n signal: composedSignal(opts?.signal),\n });\n }\n\n private async request<T>(\n method: string,\n opts?: {\n body?: Record<string, unknown> | undefined;\n query?: URLSearchParams | undefined;\n signal?: AbortSignal | undefined;\n },\n ): Promise<T> {\n const query = opts?.query?.toString();\n const url = `${this.baseUrl}/${method}${query ? `?${query}` : ''}`;\n const init: RequestInit = {\n method: opts?.body ? 'POST' : 'GET',\n };\n if (opts?.signal) init.signal = opts.signal;\n if (opts?.body) {\n init.headers = { 'Content-Type': 'application/json' };\n init.body = JSON.stringify(opts.body);\n }\n\n let response: Response;\n try {\n const fetchImpl = this.fetchOverride ?? globalThis.fetch;\n response = await fetchImpl(url, init);\n } catch (error) {\n const detail = this.redact(errorDetail(error));\n const aborted = error instanceof Error && error.name === 'AbortError';\n throw new TelegramNetworkError(method, detail, aborted);\n }\n\n let decoded: unknown;\n try {\n decoded = await response.json();\n } catch (error) {\n if (!response.ok) {\n throw new TelegramHttpError(method, response.status, this.redact(response.statusText));\n }\n throw new TelegramResponseParseError(method, this.redact(errorDetail(error)));\n }\n\n if (!isRecord(decoded) || typeof decoded.ok !== 'boolean') {\n if (!response.ok) {\n throw new TelegramHttpError(method, response.status, this.redact(response.statusText));\n }\n throw new TelegramResponseParseError(method, 'expected a Bot API response envelope');\n }\n\n const envelope = decoded as unknown as TelegramApiEnvelope<T>;\n if (!envelope.ok) {\n throw new TelegramBotApiError(method, {\n errorCode: envelope.error_code,\n httpStatus: response.status,\n description: this.redact(envelope.description ?? 'Unknown Bot API error'),\n retryAfterSeconds: envelope.parameters?.retry_after,\n migrateToChatId: envelope.parameters?.migrate_to_chat_id,\n });\n }\n if (!response.ok) {\n throw new TelegramHttpError(method, response.status, this.redact(response.statusText));\n }\n if (envelope.result === undefined || envelope.result === null) {\n throw new TelegramResponseParseError(method, 'successful response did not include result');\n }\n\n return envelope.result;\n }\n\n private redact(value: string): string {\n return value.replaceAll(this.token, '[REDACTED]');\n }\n}\n", "import type { Logger } from '@wrongstack/core/types';\nimport {\n TelegramApiClient,\n TelegramBotApiError,\n TelegramNetworkError,\n abortableSleep,\n classifyRetry,\n type TelegramApiCallbackQuery,\n type TelegramApiMessage,\n} from './api-client.js';\nimport type { OffsetStore } from './offset-store.js';\nimport type { PollLock } from './poll-lock.js';\n\nexport interface TelegramBotResponse<T> {\n ok: true;\n result: T;\n}\n\n// ---------------------------------------------------------------------------\n// Incoming message shape emitted as a custom event\n// ---------------------------------------------------------------------------\n\nexport interface TelegramIncomingMessage {\n messageId: number;\n chatId: number;\n chatType: string;\n userId?: number | undefined;\n userName?: string | undefined;\n text: string;\n timestamp: number;\n}\n\nexport interface TelegramApprovalResult {\n approved: boolean;\n fromUser: string;\n fromUserId?: number | undefined;\n}\n\nexport interface TelegramApprovalRequestInput {\n requestId: string;\n sessionId: string;\n expectedChatId: string | number;\n expectedUserIds: readonly (string | number)[];\n /** Group/supergroup callbacks are rejected unless this was explicitly enabled. */\n allowGroup: boolean;\n expiresAt: number;\n /** Cancels the request when its owning tool execution is aborted. */\n signal?: AbortSignal | undefined;\n}\n\ntype TelegramApprovalRequestState = 'pending' | 'resolved' | 'expired' | 'cancelled';\n\ninterface TelegramApprovalRequest {\n requestId: string;\n sessionId: string;\n expectedChatId: string;\n expectedUserIds: ReadonlySet<string>;\n allowGroup: boolean;\n promptMessageId?: number | undefined;\n pendingCallbacks: TelegramApiCallbackQuery[];\n expiresAt: number;\n state: TelegramApprovalRequestState;\n resolve: (value: TelegramApprovalResult) => void;\n timer: ReturnType<typeof setTimeout>;\n signal?: AbortSignal | undefined;\n abortHandler?: (() => void) | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Bot options\n// ---------------------------------------------------------------------------\n\nexport interface TelegramBotOptions {\n token: string;\n pollIntervalSec: number;\n allowedUsers: Set<string>;\n allowedChats: Set<string>;\n /** Max messages to buffer for the agent to read. Default: 50. */\n bufferSize: number;\n log: Logger;\n /**\n * Resolved on every outbound send so live `parseMode` config changes\n * (via `api.onConfigChange`) take effect without restarting the plugin.\n * Empty string or `undefined` \u2192 plain text. See `TelegramPluginConfig.parseMode`.\n */\n getParseMode?: () => '' | 'HTML' | 'MarkdownV2' | undefined;\n /** Called for each incoming message that passes allowlist checks. */\n onMessage(msg: TelegramIncomingMessage): void;\n /**\n * Optional typed offset store. When provided, the polling offset is persisted\n * atomically on every successful poll and restored on startup, preventing\n * message replay after crashes or restarts.\n */\n offsetStore?: OffsetStore | undefined;\n /**\n * Optional cross-process single-poller lock. Telegram allows one\n * `getUpdates` consumer per token; when another wstack instance holds the\n * lock, this bot stands by (no polling) and takes over once the holder\n * stops or its heartbeat goes stale.\n */\n lock?: PollLock | undefined;\n /** How often a standby instance retries acquiring the lock. Default: 15s. */\n standbyRetryMs?: number | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Bot\n// ---------------------------------------------------------------------------\n\nexport class TelegramBot {\n private readonly api: TelegramApiClient;\n private readonly pollIntervalMs: number;\n private readonly allowedUsers: Set<string>;\n private readonly allowedChats: Set<string>;\n private readonly log: Logger;\n private readonly onMessage: (msg: TelegramIncomingMessage) => void;\n private readonly controller = new AbortController();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private pollActive = false;\n private offset = 0;\n /**\n * Consecutive HTTP 409 (\"another getUpdates in flight\") responses. Two\n * wstack instances polling the same bot token used to fight at full poll\n * speed forever, erroring on every cycle. After CONFLICT_BACKOFF_AFTER\n * consecutive conflicts this instance backs off to a slow poll and warns\n * once; any successful poll resets to the normal cadence.\n */\n private conflictStreak = 0;\n private static readonly CONFLICT_BACKOFF_AFTER = 3;\n private static readonly CONFLICT_POLL_MS = 60_000;\n private _startedAt: number | null = null;\n /** Typed offset store for atomic polling-cursor persistence. */\n private readonly offsetStore?: OffsetStore | undefined;\n /** Single-poller election across wstack instances sharing this token. */\n private readonly lock?: PollLock | undefined;\n private readonly standbyRetryMs: number;\n private readonly getParseMode?: (() => '' | 'HTML' | 'MarkdownV2' | undefined) | undefined;\n private standbyTimer: ReturnType<typeof setTimeout> | null = null;\n private standbyAnnounced = false;\n\n // Circular buffer for incoming messages\n private readonly bufferMax: number;\n private readonly buffer: TelegramIncomingMessage[] = [];\n\n // Pending approval requests keyed by request identity, not raw callback\n // data. Each request binds both yes/no actions to its originating session,\n // target chat, intended users, prompt message, and expiry.\n private readonly callbackWaiters = new Map<string, TelegramApprovalRequest>();\n\n constructor(opts: TelegramBotOptions) {\n this.api = new TelegramApiClient({ token: opts.token });\n this.pollIntervalMs = opts.pollIntervalSec * 1000;\n this.allowedUsers = opts.allowedUsers;\n this.allowedChats = opts.allowedChats;\n this.bufferMax = opts.bufferSize;\n this.log = opts.log;\n this.onMessage = opts.onMessage;\n this.offsetStore = opts.offsetStore;\n this.lock = opts.lock;\n this.standbyRetryMs = opts.standbyRetryMs ?? 15_000;\n this.getParseMode = opts.getParseMode;\n if (this.lock) {\n this.lock.onLost = () => this.handleLockLost();\n }\n\n // Restore persisted offset so a crash/restart doesn't cause message replay.\n if (this.offsetStore) {\n void this.loadOffset();\n }\n }\n\n // ------------------------------------------------------------------\n // Lifecycle\n // ------------------------------------------------------------------\n\n /** Start polling for updates. Idempotent. */\n start(): void {\n if (this.pollActive) return;\n this.pollActive = true;\n this._startedAt = Date.now();\n this.acquireAndPoll();\n }\n\n /** Stop polling and cancel all in-flight requests. */\n stop(): void {\n this.pollActive = false;\n this.controller.abort();\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n if (this.standbyTimer) {\n clearTimeout(this.standbyTimer);\n this.standbyTimer = null;\n }\n // Reject any pending approval requests so the host doesn't hang.\n for (const requestId of Array.from(this.callbackWaiters.keys())) {\n this.settleApproval(requestId, 'cancelled', {\n approved: false,\n fromUser: 'shutdown',\n });\n }\n this.lock?.release();\n this.log.info('Telegram bot stopped');\n }\n\n /** True when the bot is started but waiting for the poll lock. */\n get standby(): boolean {\n return this.pollActive && this.lock !== undefined && !this.lock.held;\n }\n\n /**\n * Acquire the poll lock (when configured) and start the poll loop, or\n * stand by and retry until the current holder releases it.\n */\n private acquireAndPoll(): void {\n if (!this.pollActive) return;\n if (this.lock && !this.lock.tryAcquire()) {\n if (!this.standbyAnnounced) {\n this.standbyAnnounced = true;\n this.log.info(\n 'Telegram: another wstack instance is already polling this bot token \u2014 standing by; will take over when it stops.',\n );\n }\n this.standbyTimer = setTimeout(() => this.acquireAndPoll(), this.standbyRetryMs);\n this.standbyTimer.unref?.();\n return;\n }\n if (this.standbyAnnounced) {\n this.standbyAnnounced = false;\n this.log.info('Telegram: poll lock acquired \u2014 taking over polling.');\n } else {\n this.log.info(`Telegram bot polling started (${this.api.safeBaseUrl})`);\n }\n this.schedulePoll();\n }\n\n /** The lock was stolen while we held it \u2014 pause polling and stand by. */\n private handleLockLost(): void {\n if (!this.pollActive) return;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n this.log.warn(\n 'Telegram: poll lock lost to another instance \u2014 pausing polling and standing by.',\n );\n this.standbyAnnounced = true; // acquireAndPoll already announced via this warn\n this.standbyTimer = setTimeout(() => this.acquireAndPoll(), this.standbyRetryMs);\n this.standbyTimer.unref?.();\n }\n\n get startedAt(): number | null {\n return this._startedAt;\n }\n\n get running(): boolean {\n return this.pollActive;\n }\n\n // ------------------------------------------------------------------\n // Buffer \u2014 incoming messages the agent can read\n // ------------------------------------------------------------------\n\n /** Return buffered messages, newest first. Optionally filter by chat. */\n getMessages(opts?: {\n chatId?: string | number | undefined;\n limit?: number | undefined;\n }): TelegramIncomingMessage[] {\n let msgs = [...this.buffer].reverse();\n if (opts?.chatId) {\n const cid = String(opts.chatId);\n msgs = msgs.filter((m) => String(m.chatId) === cid);\n }\n const limit = opts?.limit ?? 20;\n return msgs.slice(0, limit);\n }\n\n /** Drop messages older than the given message ID from the buffer. */\n acknowledge(lastMessageId: number): number {\n const before = this.buffer.length;\n let i = this.buffer.length;\n while (i-- > 0) {\n const buffered = this.buffer[i];\n if (buffered && buffered.messageId <= lastMessageId) {\n this.buffer.splice(0, i + 1);\n break;\n }\n }\n return before - this.buffer.length;\n }\n\n get bufferCount(): number {\n return this.buffer.length;\n }\n\n // ------------------------------------------------------------------\n // Outgoing \u2014 send a message\n // ------------------------------------------------------------------\n\n async sendMessage(\n chatId: string | number,\n text: string,\n signal?: AbortSignal | undefined,\n ): Promise<TelegramBotResponse<TelegramApiMessage>> {\n this.log.debug(`Sending Telegram message to ${chatId} (${text.length} chars)`);\n\n let lastErr: unknown;\n for (let attempt = 1; attempt <= 3; attempt++) {\n try {\n const timeout = AbortSignal.timeout(10_000);\n const result = await this.api.sendMessage(chatId, text, {\n signal: signal ? AbortSignal.any([signal, timeout]) : timeout,\n parseMode: this.getParseMode?.(),\n });\n return { ok: true, result };\n } catch (err) {\n lastErr = err;\n const decision = classifyRetry(err, attempt);\n if (!decision.retry) {\n if (attempt > 1)\n this.log.debug(\n `Telegram sendMessage terminal error on attempt ${attempt}, not retrying`,\n );\n break;\n }\n this.log.debug(\n `Telegram sendMessage attempt ${attempt} failed, retrying in ${decision.delayMs}ms...`,\n );\n await abortableSleep(decision.delayMs, signal);\n }\n }\n throw lastErr;\n }\n\n // ------------------------------------------------------------------\n // Outgoing \u2014 send a message with an inline keyboard\n // ------------------------------------------------------------------\n\n /**\n * Send a message that has up to one row of inline buttons (Telegram's\n * `inline_keyboard`). Used by `telegram_approve` to present a\n * yes/no prompt. The keyboard payload is opaque to the bot \u2014 callers\n * pass already-encoded `callback_data` strings (\u2264 64 bytes each).\n */\n async sendMessageWithKeyboard(\n chatId: string | number,\n text: string,\n buttons: Array<{ text: string; callback_data: string }>,\n signal?: AbortSignal | undefined,\n ): Promise<TelegramBotResponse<TelegramApiMessage>> {\n let lastErr: unknown;\n for (let attempt = 1; attempt <= 3; attempt++) {\n try {\n const timeout = AbortSignal.timeout(10_000);\n const result = await this.api.sendMessageWithKeyboard(chatId, text, buttons, {\n signal: signal ? AbortSignal.any([signal, timeout]) : timeout,\n parseMode: this.getParseMode?.(),\n });\n return { ok: true, result };\n } catch (err) {\n lastErr = err;\n const decision = classifyRetry(err, attempt);\n if (!decision.retry) {\n if (attempt > 1)\n this.log.debug(\n `Telegram sendMessageWithKeyboard terminal error on attempt ${attempt}, not retrying`,\n );\n break;\n }\n await abortableSleep(decision.delayMs, signal);\n }\n }\n throw lastErr;\n }\n\n // ------------------------------------------------------------------\n // Health\n // ------------------------------------------------------------------\n\n async health(signal?: AbortSignal | undefined): Promise<{\n ok: boolean;\n username?: string | undefined;\n error?: string | undefined;\n }> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), 5000);\n try {\n const timeout = AbortSignal.timeout(5_000);\n const deadline = AbortSignal.any([ctrl.signal, timeout]);\n const combined = signal ? AbortSignal.any([signal, deadline]) : deadline;\n const user = await this.api.getMe({ signal: combined });\n return { ok: true, username: user.username };\n } catch (err) {\n if (err instanceof TelegramBotApiError) return { ok: false, error: err.description };\n if (err instanceof TelegramNetworkError) return { ok: false, error: err.detail };\n return { ok: false, error: (err as Error).message };\n } finally {\n clearTimeout(timer);\n }\n }\n\n // ------------------------------------------------------------------\n // Polling\n // ------------------------------------------------------------------\n\n private schedulePoll(): void {\n if (!this.pollActive) return;\n // Lost the poll lock mid-flight \u2014 the standby retry loop owns recovery.\n if (this.lock && !this.lock.held) return;\n const delay =\n this.conflictStreak >= TelegramBot.CONFLICT_BACKOFF_AFTER\n ? TelegramBot.CONFLICT_POLL_MS\n : this.pollIntervalMs;\n this.pollTimer = setTimeout(() => {\n void this.poll().finally(() => this.schedulePoll());\n }, delay);\n }\n\n private async poll(): Promise<void> {\n try {\n const updates = await this.api.getUpdates({\n offset: this.offset,\n timeoutSeconds: 10,\n deadlineMs: 15_000,\n signal: this.controller.signal,\n });\n this.conflictStreak = 0;\n\n for (const upd of updates) {\n this.offset = upd.update_id + 1;\n if (upd.callback_query) {\n void this.dispatchCallback(upd.callback_query);\n continue;\n }\n\n const raw = upd.message ?? upd.edited_message;\n if (!raw?.text) continue;\n this.processMessage({ ...raw, text: raw.text });\n }\n\n // P1.6: commit the cursor only after processing. An empty poll or a\n // 0 -> 0 idle tick MUST NOT trigger a write. Require updates.length > 0\n // so a successful but empty poll leaves the persisted offset\n // unchanged \u2014 preserves the replay dedup boundary on restart.\n if (this.offsetStore && updates.length > 0) void this.saveOffset();\n } catch (err) {\n if (err instanceof TelegramNetworkError && err.aborted) return;\n if (err instanceof TelegramBotApiError && err.errorCode === 409) {\n this.conflictStreak++;\n if (this.conflictStreak === TelegramBot.CONFLICT_BACKOFF_AFTER) {\n this.log.warn(\n this.lock\n ? 'Telegram: another consumer outside this machine is polling this bot token (HTTP 409) \u2014 backing off to 60s polls. Check other machines/bots using this token, or a registered webhook (deleteWebhook).'\n : 'Telegram: another instance is polling this bot token (HTTP 409) \u2014 backing off to 60s polls until it stops.',\n );\n }\n this.log.debug(`Telegram getUpdates failed: ${err.description}`);\n return;\n }\n this.log.debug(`Telegram poll error: ${(err as Error).message}`);\n }\n }\n\n /**\n * Apply the inbound identity policy to every update type. A non-empty set is\n * a mandatory constraint: missing identity fails closed instead of bypassing\n * the allowlist. An empty set leaves that identity dimension unrestricted.\n */\n private inboundDenialReason(\n userId: string | undefined,\n chatId: string | undefined,\n ): 'user' | 'chat' | undefined {\n // Check the chat first so a doubly-blocked message cannot trigger an\n // unauthorized-user reply into an arbitrary, non-allowlisted chat.\n if (this.allowedChats.size > 0 && (chatId === undefined || !this.allowedChats.has(chatId))) {\n return 'chat';\n }\n if (this.allowedUsers.size > 0 && (userId === undefined || !this.allowedUsers.has(userId))) {\n return 'user';\n }\n return undefined;\n }\n\n private processMessage(msg: TelegramApiMessage & { text: string }): void {\n const chatId = String(msg.chat.id);\n const userId = msg.from ? String(msg.from.id) : undefined;\n const denialReason = this.inboundDenialReason(userId, chatId);\n\n if (denialReason === 'user') {\n this.log.debug(`Ignoring message from user ${userId ?? 'unknown'} (not in allowedUsers)`);\n void this.sendMessage(chatId, '\u26D4 You are not authorized to interact with this bot.');\n return;\n }\n if (denialReason === 'chat') {\n this.log.debug(`Ignoring message from chat ${chatId} (not in allowedChats)`);\n return;\n }\n\n const incoming: TelegramIncomingMessage = {\n messageId: msg.message_id,\n chatId: msg.chat.id,\n chatType: msg.chat.type,\n userId: msg.from?.id,\n userName: msg.from?.username ?? msg.from?.first_name,\n text: msg.text,\n timestamp: msg.date * 1000,\n };\n\n // Push to circular buffer\n this.buffer.push(incoming);\n while (this.buffer.length > this.bufferMax) this.buffer.shift();\n\n this.onMessage(incoming);\n }\n\n /**\n * Resolve a pending approval request exactly once and record its terminal\n * state before removing it from the live registry.\n */\n private settleApproval(\n requestId: string,\n state: Exclude<TelegramApprovalRequestState, 'pending'>,\n result: TelegramApprovalResult,\n ): boolean {\n const request = this.callbackWaiters.get(requestId);\n if (request?.state !== 'pending') return false;\n request.state = state;\n clearTimeout(request.timer);\n if (request.signal && request.abortHandler) {\n request.signal.removeEventListener('abort', request.abortHandler);\n }\n request.pendingCallbacks.length = 0;\n this.callbackWaiters.delete(requestId);\n request.resolve(result);\n return true;\n }\n\n private async dispatchCallback(cq: TelegramApiCallbackQuery): Promise<void> {\n const key = cq.data ?? '';\n const action = /^approve:([^:]+):(yes|no)$/.exec(key);\n const requestId = action?.[1];\n const request = requestId ? this.callbackWaiters.get(requestId) : undefined;\n\n // Use the same coarse inbound policy as messages before applying the\n // request-specific identity binding below. Unauthorized callbacks are\n // acknowledged but never consume the valid user's pending request.\n const userId = cq.from?.id !== undefined ? String(cq.from.id) : undefined;\n const chatId = cq.message?.chat.id !== undefined ? String(cq.message.chat.id) : undefined;\n const denialReason = this.inboundDenialReason(userId, chatId);\n if (denialReason) {\n const identity = denialReason === 'user' ? (userId ?? 'unknown') : (chatId ?? 'unknown');\n this.log.warn(\n `Ignoring callback_query from non-allowlisted ${denialReason} ${identity} (data=\"${key}\") \u2014 possible hijack attempt.`,\n );\n await this.answerCallback(cq.id, '\u26D4 Not authorized', true);\n return;\n }\n\n if (!request || !requestId || !action) {\n await this.answerCallback(cq.id, 'Approval request unavailable', true);\n this.log.debug(`Unmatched callback_query data=\"${key}\" (no pending approval request)`);\n return;\n }\n\n if (Date.now() >= request.expiresAt) {\n await this.answerCallback(cq.id, 'Approval request expired', true);\n this.settleApproval(requestId, 'expired', { approved: false, fromUser: 'timeout' });\n return;\n }\n\n // The request is registered before sendMessage so a callback can arrive\n // before the Bot API response supplies message_id. Keep exactly that\n // callback queued until bindApprovalPrompt attaches the sent prompt.\n if (request.promptMessageId === undefined) {\n request.pendingCallbacks.push(cq);\n return;\n }\n\n const messageId = cq.message?.message_id;\n const chatType = cq.message?.chat.type;\n const wrongIdentity =\n userId === undefined ||\n chatId !== request.expectedChatId ||\n !request.expectedUserIds.has(userId) ||\n messageId !== request.promptMessageId ||\n (chatType !== 'private' && !request.allowGroup);\n if (wrongIdentity) {\n this.log.warn(\n `Ignoring callback_query that does not match approval request ${request.requestId} in session ${request.sessionId}.`,\n );\n await this.answerCallback(cq.id, '\u26D4 Not authorized for this approval', true);\n return;\n }\n\n const approved = action[2] === 'yes';\n const fromUser = cq.from?.username ?? cq.from?.first_name ?? `user:${userId}`;\n const resolved = this.settleApproval(requestId, 'resolved', {\n approved,\n fromUser,\n fromUserId: cq.from?.id,\n });\n await this.answerCallback(\n cq.id,\n resolved ? (approved ? 'Approved \u2713' : 'Denied \u2717') : 'Approval request unavailable',\n !resolved,\n );\n }\n\n /**\n * POST /answerCallbackQuery for a callback. Best-effort: failures are\n * logged at debug and swallowed \u2014 the caller's resolve() must not depend\n * on the ack reaching Telegram (the user may get a \"loading\" spinner if\n * it fails, but the agent's approval flow continues normally).\n */\n private async answerCallback(\n callbackQueryId: string,\n text: string,\n showAlert: boolean,\n ): Promise<void> {\n try {\n await this.api.answerCallbackQuery(callbackQueryId, text, showAlert, {\n signal: AbortSignal.timeout(5_000),\n });\n } catch (err) {\n this.log.debug(`answerCallbackQuery failed: ${(err as Error).message}`);\n }\n }\n\n /**\n * Register one approval request before its prompt is sent. The returned\n * promise owns the request's only timer and resolves on one terminal event.\n */\n awaitApproval(input: TelegramApprovalRequestInput): Promise<TelegramApprovalResult> {\n if (input.expectedUserIds.length === 0) {\n throw new Error('Telegram approval requires at least one expected user ID.');\n }\n if (this.callbackWaiters.has(input.requestId)) {\n throw new Error(`Telegram approval request ${input.requestId} is already pending.`);\n }\n\n return new Promise((resolve) => {\n const delayMs = Math.max(0, input.expiresAt - Date.now());\n const timer = setTimeout(() => {\n this.settleApproval(input.requestId, 'expired', {\n approved: false,\n fromUser: 'timeout',\n });\n }, delayMs);\n const request: TelegramApprovalRequest = {\n requestId: input.requestId,\n sessionId: input.sessionId,\n expectedChatId: String(input.expectedChatId),\n expectedUserIds: new Set(input.expectedUserIds.map(String)),\n allowGroup: input.allowGroup,\n pendingCallbacks: [],\n expiresAt: input.expiresAt,\n state: 'pending',\n resolve,\n timer,\n signal: input.signal,\n };\n if (input.signal) {\n request.abortHandler = () => {\n this.settleApproval(input.requestId, 'cancelled', {\n approved: false,\n fromUser: 'aborted',\n });\n };\n }\n this.callbackWaiters.set(input.requestId, request);\n if (input.signal?.aborted) {\n request.abortHandler?.();\n } else if (input.signal && request.abortHandler) {\n input.signal.addEventListener('abort', request.abortHandler, { once: true });\n }\n });\n }\n\n /**\n * Attach the Bot API response's prompt message ID to an existing request.\n * Any callback that arrived during the send is replayed against the fully\n * bound identity without allocating a second waiter or timer.\n */\n bindApprovalPrompt(requestId: string, promptMessageId: number): boolean {\n const request = this.callbackWaiters.get(requestId);\n if (request?.state !== 'pending' || request.promptMessageId !== undefined) return false;\n request.promptMessageId = promptMessageId;\n const pending = request.pendingCallbacks.splice(0);\n for (const callback of pending) {\n void this.dispatchCallback(callback);\n }\n return true;\n }\n\n /** Cancel a request that cannot reach a valid terminal callback. */\n cancelApproval(requestId: string, fromUser = 'cancelled'): boolean {\n return this.settleApproval(requestId, 'cancelled', { approved: false, fromUser });\n }\n\n private async loadOffset(): Promise<void> {\n if (!this.offsetStore) return;\n try {\n const saved = this.offsetStore.read();\n if (saved !== null) {\n this.offset = saved;\n this.log.debug(`Telegram polling offset restored: ${this.offset}`);\n }\n } catch {\n // Best-effort \u2014 a corrupt or missing file starts from 0.\n }\n }\n\n private async saveOffset(): Promise<void> {\n if (!this.offsetStore) return;\n try {\n this.offsetStore.write(this.offset);\n } catch (err) {\n this.log.debug(`Failed to persist Telegram offset: ${err}`);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Truncate text to fit Telegram's 4096-char message limit.\n * Preserves semantic boundaries in this priority order:\n * 1. Paragraph break (double newline)\n * 2. Sentence break (. ! ? followed by space/newline)\n * 3. Word break (space)\n * 4. Hard cut with ellipsis\n *\n * When a clean boundary is found, appends \"\u2026\" to signal intentional truncation.\n */\n/**\n * Maximum permitted output length. Telegram rejects messages longer than\n * 4096 characters; we clamp the requested cap at this value so a\n * misconfigured caller cannot silently violate the platform contract.\n */\nconst MAX_TELEGRAM_MESSAGE_LENGTH = 4096;\n\nexport function truncateForTelegram(text: string, maxLen = 4000): string {\n // P1.8 explicit message-length contract: the caller's cap is the\n // binding contract, but it is clamped at Telegram's hard 4096-char\n // limit so a misconfigured `maxLen > 4096` cannot silently produce\n // output that the platform will reject.\n const effectiveMaxLen = Math.min(maxLen, MAX_TELEGRAM_MESSAGE_LENGTH);\n if (text.length <= effectiveMaxLen) return text;\n\n // Reserve room for truncation suffix\n const cutoff = effectiveMaxLen - 30;\n if (cutoff <= 0) return `${text.slice(0, effectiveMaxLen - 1)}\u2026`;\n\n const searchEnd = Math.min(text.length, effectiveMaxLen);\n\n // 1. Paragraph boundary (double newline)\n const paraIdx = text.lastIndexOf('\\n\\n', searchEnd);\n if (paraIdx > cutoff) {\n return `${text.slice(0, paraIdx)}\\n\\n\u2026`;\n }\n\n // 2. Single newline boundary\n const nlIdx = text.lastIndexOf('\\n', searchEnd);\n if (nlIdx > cutoff) {\n return `${text.slice(0, nlIdx)}\\n\u2026`;\n }\n\n // 3. Sentence boundary (. ! ? followed by space or newline)\n const sentenceRe = /[.!?](?=\\s)/g;\n let match: RegExpExecArray | null;\n let sentenceIdx = -1;\n match = sentenceRe.exec(text);\n while (match !== null) {\n if (match.index >= searchEnd) break;\n if (match.index > cutoff) sentenceIdx = match.index + 1;\n match = sentenceRe.exec(text);\n }\n if (sentenceIdx > cutoff) {\n return `${text.slice(0, sentenceIdx)}\u2026`;\n }\n\n // 4. Word boundary (space)\n const spaceIdx = text.lastIndexOf(' ', searchEnd);\n if (spaceIdx > cutoff) {\n return `${text.slice(0, spaceIdx)} \u2026`;\n }\n\n // 5. Hard cut\n return `${text.slice(0, effectiveMaxLen - 20)}\u2026[+${text.length - effectiveMaxLen + 20} chars]`;\n}\n\n/**\n * Escape HTML special chars for Telegram's HTML parse mode.\n */\nexport function escapeHtml(text: string): string {\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n", "import { type PluginAPI, resolvePluginConfig } from '@wrongstack/core/plugin';\nimport type { Config, PluginConfigFields } from '@wrongstack/core/types';\n\nexport const PLUGIN_NAME = 'telegram';\nexport const PLUGIN_CONFIG_ALIASES = ['@wrongstack/telegram'] as const;\n\nexport type TelegramInboundMode = 'disabled' | 'paired' | 'allowlist' | 'public';\n\nconst INBOUND_MODES = ['disabled', 'paired', 'allowlist', 'public'] as const;\n\nexport interface TelegramPluginConfig {\n /** Telegram Bot API token (from @BotFather). */\n botToken: string;\n /**\n * Default chat ID for outgoing notifications.\n * The agent's `telegram_send` tool can override per-call.\n */\n notifyChatId?: string | number | undefined;\n /**\n * Controls who may send inbound messages to the bot. Defaults to `disabled`\n * for new/unpaired configurations. Legacy configurations are migrated to\n * `allowlist` when IDs exist, or `paired` when `notifyChatId` exists.\n */\n inboundMode?: TelegramInboundMode | undefined;\n /** List of user IDs accepted when `inboundMode` is `allowlist`. */\n allowedUsers?: Array<string | number> | undefined;\n /** List of chat IDs accepted when `inboundMode` is `allowlist`. */\n allowedChats?: Array<string | number> | undefined;\n /** Additional trusted targets for outbound sends beyond `notifyChatId`. */\n allowedOutboundChats?: Array<string | number> | undefined;\n /** Polling interval in seconds (default: 2). */\n pollIntervalSec?: number | undefined;\n /** Notify on Telegram when a session ends. */\n notifyOnSessionEnd?: boolean | undefined;\n /** Notify when a tool runs longer than this threshold (ms). Set 0 to disable. */\n longToolThresholdMs?: number | undefined;\n /** Notify (humanized) when a `delegate` subagent finishes. Default: true. */\n notifyOnDelegate?: boolean | undefined;\n /** Maximum message length for Telegram (Telegram caps at 4096). */\n maxMessageLength?: number | undefined;\n /**\n * Path to a file that stores the Telegram polling offset. When set,\n * the offset is persisted on every successful poll and restored on startup,\n * preventing message replay after crashes or restarts.\n * The directory must already exist and be writable.\n */\n offsetStoragePath?: string | undefined;\n /**\n * Elect a single poller per bot token across wstack instances (default:\n * true). Telegram allows one `getUpdates` consumer per token; without this,\n * two instances sharing a token fight and get HTTP 409 on every poll.\n * Extra instances stand by and take over when the active poller stops.\n * Set false only if this is guaranteed to be the sole consumer.\n */\n singleInstanceLock?: boolean | undefined;\n /**\n * Per-chat pending-message cap for the outbound queue. Older pending\n * notification entries are dropped when this is exceeded; manual\n * telegram_send entries surface the overflow as an error. Default: 32.\n */\n outboundQueuePerChat?: number | undefined;\n /** Maximum concurrent outbound sends across all chats. Default: 4. */\n outboundQueueConcurrency?: number | undefined;\n /** Permit group-chat approvals only when an explicit user allowlist also matches. */\n allowGroupApprovals?: boolean | undefined;\n /** Per-chat rate limit (tokens per second). Default: 0.33 (\u224820 msg/min). */\n rateLimitTokensPerSecond?: number | undefined;\n /** Per-chat rate limit burst size. Default: 4. */\n rateLimitBurst?: number | undefined;\n /**\n * Telegram parse mode for message text formatting. Supports:\n * - `'HTML'` \u2014 `<b>bold</b>`, `<i>italic</i>`, `<a href=\"...\">link</a>`, `<code>mono</code>`, `<pre>code block</pre>`\n * - `'MarkdownV2'` \u2014 `*bold*`, `_italic_`, `[link](url)`, `` `code` ``, ```pre```\n * - unset / `''` \u2014 plain text (no formatting)\n */\n parseMode?: '' | 'HTML' | 'MarkdownV2' | undefined;\n}\n\nexport const DEFAULT_CONFIG: Required<\n Omit<TelegramPluginConfig, 'botToken' | 'notifyChatId' | 'offsetStoragePath'>\n> = {\n inboundMode: 'disabled',\n allowedUsers: [],\n allowedChats: [],\n allowedOutboundChats: [],\n pollIntervalSec: 2,\n notifyOnSessionEnd: false,\n longToolThresholdMs: 30_000,\n notifyOnDelegate: true,\n maxMessageLength: 4000,\n singleInstanceLock: true,\n outboundQueuePerChat: 32,\n outboundQueueConcurrency: 4,\n allowGroupApprovals: false,\n rateLimitTokensPerSecond: 0.33,\n rateLimitBurst: 4,\n parseMode: '',\n};\n\nexport const TELEGRAM_CONFIG_FIELDS = {\n botToken: { lifecycle: 'restart', secret: true },\n notifyChatId: { lifecycle: 'restart' },\n inboundMode: { lifecycle: 'hot' },\n allowedUsers: { lifecycle: 'hot' },\n allowedChats: { lifecycle: 'hot' },\n allowedOutboundChats: { lifecycle: 'hot' },\n allowGroupApprovals: { lifecycle: 'hot' },\n pollIntervalSec: { lifecycle: 'hot' },\n notifyOnSessionEnd: { lifecycle: 'hot' },\n longToolThresholdMs: { lifecycle: 'hot' },\n notifyOnDelegate: { lifecycle: 'hot' },\n maxMessageLength: { lifecycle: 'hot' },\n offsetStoragePath: { lifecycle: 'immutable' },\n singleInstanceLock: { lifecycle: 'restart' },\n outboundQueuePerChat: { lifecycle: 'restart' },\n outboundQueueConcurrency: { lifecycle: 'restart' },\n rateLimitTokensPerSecond: { lifecycle: 'hot', description: 'Per-chat rate limit (tokens/sec)' },\n rateLimitBurst: { lifecycle: 'hot', description: 'Per-chat rate limit burst size' },\n parseMode: { lifecycle: 'hot', description: 'Telegram parse mode: HTML, MarkdownV2, or empty for plain text' },\n} as const satisfies PluginConfigFields<TelegramPluginConfig>;\n\nexport const telegramConfigSchema = {\n type: 'object',\n properties: {\n botToken: { type: 'string', description: 'Telegram Bot API token from @BotFather' },\n notifyChatId: {\n oneOf: [{ type: 'string' }, { type: 'integer' }],\n description: 'Default chat ID for outgoing notifications',\n },\n inboundMode: {\n type: 'string',\n enum: [...INBOUND_MODES],\n default: 'disabled',\n description:\n 'Inbound access: disabled, paired to notifyChatId, restricted by allowlists, or explicitly public',\n },\n allowedUsers: {\n type: 'array',\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\n description: 'User IDs accepted when inboundMode is allowlist',\n },\n allowedChats: {\n type: 'array',\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\n description: 'Chat IDs accepted when inboundMode is allowlist',\n },\n allowedOutboundChats: {\n type: 'array',\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\n description: 'Additional trusted targets for outbound Telegram sends',\n },\n pollIntervalSec: {\n type: 'integer',\n minimum: 1,\n maximum: 60,\n description: 'Polling interval in seconds',\n },\n notifyOnSessionEnd: { type: 'boolean' },\n longToolThresholdMs: { type: 'integer', minimum: 0 },\n notifyOnDelegate: { type: 'boolean' },\n maxMessageLength: { type: 'integer', minimum: 100, maximum: 4096 },\n offsetStoragePath: { type: 'string' },\n singleInstanceLock: {\n type: 'boolean',\n description:\n 'Elect a single getUpdates poller per bot token across wstack instances (default true)',\n },\n outboundQueuePerChat: {\n type: 'integer',\n minimum: 1,\n maximum: 1000,\n description: 'Per-chat pending outbound-message cap (default 32)',\n },\n outboundQueueConcurrency: {\n type: 'integer',\n minimum: 1,\n maximum: 64,\n description: 'Maximum concurrent outbound sends across all chats (default 4)',\n },\n allowGroupApprovals: { type: 'boolean' },\n rateLimitTokensPerSecond: {\n type: 'number',\n minimum: 0.1,\n maximum: 100,\n description: 'Per-chat rate limit in tokens per second (default: 0.33 \u224820 msg/min)',\n },\n rateLimitBurst: {\n type: 'integer',\n minimum: 1,\n maximum: 100,\n description: 'Per-chat burst size (default: 1)',\n },\n parseMode: {\n type: 'string',\n enum: ['', 'HTML', 'MarkdownV2'],\n description: 'Telegram parse mode: HTML, MarkdownV2, or empty for plain text',\n },\n },\n required: ['botToken'],\n};\n\nexport function readTelegramConfig(\n api: Pick<PluginAPI, 'config'> & Partial<Pick<PluginAPI, 'log'>>,\n): Required<Omit<TelegramPluginConfig, 'notifyChatId' | 'offsetStoragePath'>> &\n Pick<TelegramPluginConfig, 'notifyChatId' | 'offsetStoragePath'> {\n const resolution = resolvePluginConfig({\n name: PLUGIN_NAME,\n aliases: PLUGIN_CONFIG_ALIASES,\n config: api.config,\n });\n const opts = resolution.options as unknown as TelegramPluginConfig;\n const inboundMode = resolveInboundMode(opts, {\n configured: resolution.configured,\n warn: api.log?.warn.bind(api.log),\n });\n\n return {\n ...DEFAULT_CONFIG,\n ...opts,\n inboundMode,\n };\n}\n\n/**\n * Read the Telegram config section from a raw `Config` snapshot.\n * Used by `api.onConfigChange` callbacks that receive `(next: Config, prev: Config)`\n * rather than a `PluginAPI`. Delegates to {@link readTelegramConfig} so the\n * merge logic (legacy plugins, extension opts, inbound-mode resolution,\n * defaults) stays in one place.\n */\nexport function readTelegramConfigFromConfig(cfg: Config): TelegramPluginConfig {\n return readTelegramConfig({ config: cfg });\n}\n\nfunction resolveInboundMode(\n opts: TelegramPluginConfig,\n migration: { configured: boolean; warn?: ((message: string) => void) | undefined },\n): TelegramInboundMode {\n if (opts.inboundMode !== undefined) {\n if (!INBOUND_MODES.includes(opts.inboundMode)) {\n throw new Error(\n `Invalid telegram inboundMode \"${String(opts.inboundMode)}\". Expected one of: ${INBOUND_MODES.join(', ')}.`,\n );\n }\n if (\n opts.inboundMode === 'allowlist' &&\n !hasEntries(opts.allowedUsers) &&\n !hasEntries(opts.allowedChats)\n ) {\n throw new Error(\n 'Telegram inboundMode \"allowlist\" requires at least one allowedUsers or allowedChats entry.',\n );\n }\n if (opts.inboundMode === 'paired' && opts.notifyChatId === undefined) {\n throw new Error('Telegram inboundMode \"paired\" requires notifyChatId.');\n }\n return opts.inboundMode;\n }\n\n if (hasEntries(opts.allowedUsers) || hasEntries(opts.allowedChats)) return 'allowlist';\n\n const inferredMode: TelegramInboundMode = opts.notifyChatId === undefined ? 'disabled' : 'paired';\n if (migration.configured) {\n migration.warn?.(\n `Telegram inbound access no longer defaults to public when allowedUsers and allowedChats are empty; inferred inboundMode \"${inferredMode}\". Set inboundMode \"public\" explicitly to preserve legacy allow-all behavior.`,\n );\n }\n return inferredMode;\n}\n\nfunction hasEntries(values: Array<string | number> | undefined): boolean {\n return Array.isArray(values) && values.length > 0;\n}\n", "// ---------------------------------------------------------------------------\n// Secret redaction for outbound Telegram messages.\n//\n// Mirrors `redactCommand` from `@wrongstack/tools` (process-registry.ts:66)\n// without taking a dependency on the tools package. The regex set is the\n// same one used by `bash`/`exec`/`_spawn-stream` to redact session JSONL,\n// crash dumps, and `/ps` output. The Telegram notification path is the\n// highest-risk exfiltration surface \u2014 tool output printed by a long bash\n// run is forwarded verbatim to a phone notification \u2014 so we run every\n// outgoing payload through this filter.\n//\n// This file is intentionally tiny and dependency-free so it can be unit\n// tested in isolation and lifted into `@wrongstack/core/utils` later if\n// more plugins need it.\n// ---------------------------------------------------------------------------\n\n// Patterns match the flag/value or env-var/secret pair. The replacement\n// callback preserves the flag name and replaces only the value, so the\n// output still reads naturally (\"--token=[REDACTED]\") and downstream\n// debugging is not destroyed.\nconst SENSITIVE_FLAG_PATTERNS: RegExp[] = [\n // --flag=value or --flag \"value\" (value captured up to next space/comma)\n /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token|database[-_]?url|connection[-_]?string)(?:[=\\s,][^\\s]*)?/gi,\n // Short flags: -t value, -p value. Only the SEPARATED form (`-t value`,\n // `-t=value`) is matched \u2014 the glued form (`-tvalue`) is intentionally\n // NOT matched because it produces too many false positives in practice\n // (`-target`, `-tries`, `-timeout` all start with `-t`). A user typing\n // `curl -tSECRET` is extremely rare; a user typing `clang -target=...`\n // is daily. The lookbehind `(?<![-\\w])` rejects `-t` inside `--token`\n // where the preceding char is another `-`.\n /(?<![-\\w])-t(?:[\\s=][^\\s,]+)/,\n /(?<![-\\w])-(?:p|password)(?:[\\s=][^\\s,]+)/gi,\n // env-var style: TOKEN=x, API_KEY=y, DATABASE_URL=z, \u2026\n /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD|DATABASE_URL|CONNECTION_STRING)\\s*[=:][^\\s,]+/gi,\n // Generic high-entropy look \u2014 only when preceded by a flag name.\n /--\\w*(?:token|key|secret|password|passwd|auth|credential)\\w*[=\\s,][A-Za-z0-9+/=]{32,}/,\n];\n\n/**\n * Replace sensitive flag values and env-style secrets with `[REDACTED]`.\n * Pure: never mutates the input. Safe to call on already-redacted text\n * (idempotent \u2014 `[REDACTED]` does not match any pattern).\n */\nexport function redactSecrets(text: string): string {\n let result = text;\n for (const pattern of SENSITIVE_FLAG_PATTERNS) {\n result = result.replace(pattern, (match) => {\n const eq = match.indexOf('=');\n const sp = match.search(/\\s/);\n let delim: string | null = null;\n let delimIdx = -1;\n if (eq !== -1) {\n delim = '=';\n delimIdx = eq;\n } else if (sp !== -1) {\n delim = match[sp] ?? null;\n delimIdx = sp;\n }\n if (delim !== null && delimIdx >= 0) {\n const flag = match.slice(0, delimIdx + 1);\n return `${flag}[REDACTED]`;\n }\n // No clear delimiter (e.g. `-tVALUE` glued to flag name) \u2014 wipe the\n // whole match. We can't tell where the flag name ends and the\n // value begins, so we redact the entire token. Using a single\n // fixed marker (not `flag+marker`) avoids leaking the original\n // value when our char-class-based flag extraction is too greedy\n // (the regex would otherwise match the value characters too).\n return '**redacted**';\n });\n }\n return result;\n}", "// ---------------------------------------------------------------------------\n// Humanizers for agent events forwarded to Telegram.\n//\n// The host emits rich structured events; this module turns them into short,\n// readable chat messages. Kept pure (no bot / IO) so it's trivially testable.\n//\n// Design rules for Telegram readability:\n// - Start with an emoji status icon so the outcome is scannable.\n// - Lead with the *headline* (what happened), then context, then stats.\n// - Never embed raw JSON. Never concatenate object dumps.\n// - Keep messages under 2000 chars so they fit one mobile screen.\n// - Use emoji sparingly \u2014 status markers only, no decoration.\n// - Run output through `redactSecrets` before formatting \u2014 a chat\n// notification is the highest-risk exfiltration surface for any token\n// that happens to land in tool output (see packages/telegram/src/redact.ts).\n// ---------------------------------------------------------------------------\n\nimport { redactSecrets } from './redact.js';\n\n// ---------------------------------------------------------------------------\n// Payload types (subsets of core event shapes)\n// ---------------------------------------------------------------------------\n\n/** Subset of the core `delegate.completed` event payload we render. */\nexport interface DelegateCompletedLike {\n target: string;\n task: string;\n ok: boolean;\n status?: string | undefined;\n summary: string;\n durationMs: number;\n iterations: number;\n toolCalls: number;\n costUsd?: number | undefined;\n subagentId?: string | undefined;\n}\n\n/** Subset of core `tool.executed` event payload. */\nexport interface ToolExecutedLike {\n name: string;\n ok: boolean;\n durationMs: number;\n /** Raw tool output \u2014 only the first 300 chars are rendered. */\n output?: string | undefined;\n}\n\n/** Subset of core `session.ended` event payload (from Usage). */\nexport interface SessionEndedLike {\n id: string;\n inputTokens: number;\n outputTokens: number;\n cacheRead?: number | undefined;\n cacheWrite?: number | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Compact human duration: `42s`, `3m`, `1.5h`. */\nexport function fmtDuration(ms: number): string {\n if (ms < 60_000) return `${Math.round(ms / 1000)}s`;\n if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`;\n return `${(ms / 3_600_000).toFixed(1)}h`;\n}\n\n/**\n * Format a numeric count of tokens for human readability.\n * Uses comma-separated thousands: 1,234, 56,789.\n */\nexport function fmtTokens(n: number): string {\n return n.toLocaleString('en-US');\n}\n\n/**\n * Try to render a tool's output as a short human-readable snippet.\n * Strips JSON braces/quoting, redacts secrets, limits to ~300 chars,\n * preserves first/last lines.\n */\nexport function fmtToolOutput(raw: string | undefined): string {\n if (!raw) return '(no output)';\n // Redact BEFORE the JSON-stripping pass so we don't transform the\n // redacted marker (e.g. `[REDACTED]` survives untouched).\n const redacted = redactSecrets(raw);\n const cleaned = redacted\n .replace(/^[{[]\\s*/, '') // strip leading JSON opening\n .replace(/\\s*[}\\]]$/, '') // strip trailing JSON closing\n .replace(/\"([^\"]+)\":/g, '$1: ') // unquote JSON keys, add space for readability\n .replace(/\\\\n/g, '\\n') // expand escaped newlines\n .replace(/\\\\\"/g, '\"') // expand escaped quotes\n .trim()\n || redacted;\n\n // Try to split into short lines; show the first 3 meaningful ones.\n const lines = cleaned.split('\\n').filter((l) => l.trim().length > 0);\n let preview = lines.slice(0, 3).join('\\n');\n if (lines.length > 3) preview += `\\n\u2026 +${lines.length - 3} more lines`;\n if (preview.length > 300) preview = `${preview.slice(0, 297)}\u2026`;\n return preview;\n}\n\n// ---------------------------------------------------------------------------\n// Event \u2192 message formatters\n// ---------------------------------------------------------------------------\n\n/**\n * Render a finished delegation as a readable Telegram message.\n *\n * Example:\n * \u2705 Delegate \u2192 bug-hunter \u00B7 success\n * Found 3 null-deref risks in auth.ts and patched the worst one\u2026\n * \u23F1 3m \u00B7 4 iter \u00B7 37 tools \u00B7 \uD83D\uDCB20.0820\n */\nexport function formatDelegateCompleted(e: DelegateCompletedLike): string {\n const icon = e.ok ? '\u2705' : '\u274C';\n const status = e.status ?? (e.ok ? 'success' : 'failed');\n const task = e.task.length > 160 ? `${e.task.slice(0, 159)}\u2026` : e.task;\n\n // Prefer the host's one-line summary; fall back to echoing the task when a\n // failure produced no summary. Both go through `redactSecrets` \u2014 a\n // delegate summary can contain raw tool output that itself leaks tokens.\n const rawBody = e.summary?.trim() || `(no summary) \u2014 ${task}`;\n const body = redactSecrets(rawBody);\n\n const stats = [\n `\u23F1 ${fmtDuration(e.durationMs)}`,\n `${e.iterations} iter`,\n `${e.toolCalls} tools`,\n ];\n if (typeof e.costUsd === 'number' && e.costUsd > 0) {\n stats.push(`\uD83D\uDCB2${e.costUsd.toFixed(4)}`);\n }\n\n return [`${icon} Delegate \u2192 ${e.target} \u00B7 ${status}`, body, stats.join(' \u00B7 ')].join('\\n');\n}\n\n/**\n * Render a long-running tool execution notification.\n *\n * Example:\n * \u2705 bash completed in 45.2s\n * pnpm test \u2014 12 suites, 47 tests passed\n * \u2026\n */\nexport function formatToolExecuted(e: ToolExecutedLike): string {\n const icon = e.ok ? '\u2705' : '\u274C';\n const sec = (e.durationMs / 1000).toFixed(1);\n const headline = `${icon} ${e.name} completed in ${sec}s`;\n\n const output = fmtToolOutput(e.output);\n // Only include output if it's short enough to be readable on mobile\n if (output === '(no output)') return headline;\n return `${headline}\\n${output}`;\n}\n\n/**\n * Render a session-end notification.\n *\n * Example:\n * \uD83C\uDFC1 Session sess_abcd ended\n * \u2B07 8,234 in \u00B7 \u2B06 3,456 out \u00B7 11,690 total\n * Cache: 1,200 read \u00B7 800 written\n */\nexport function formatSessionEnded(e: SessionEndedLike): string {\n const id = e.id.length > 8 ? e.id.slice(0, 8) : e.id;\n const total = e.inputTokens + e.outputTokens;\n\n const lines = [\n `\uD83C\uDFC1 Session ${id} ended`,\n `\u2B07 ${fmtTokens(e.inputTokens)} in \u00B7 \u2B06 ${fmtTokens(e.outputTokens)} out \u00B7 ${fmtTokens(total)} total`,\n ];\n\n // Show cache stats when available\n if (e.cacheRead || e.cacheWrite) {\n const parts: string[] = [];\n if (e.cacheRead && e.cacheRead > 0) parts.push(`${fmtTokens(e.cacheRead)} cache read`);\n if (e.cacheWrite && e.cacheWrite > 0) parts.push(`${fmtTokens(e.cacheWrite)} cache written`);\n if (parts.length > 0) lines.push(`\uD83D\uDCE6 ${parts.join(' \u00B7 ')}`);\n }\n\n return lines.join('\\n');\n}\n", "import { createHash, randomUUID } from 'node:crypto';\nimport { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport type { Logger } from '@wrongstack/core/types';\nimport { wstackGlobalRoot } from '@wrongstack/core/utils';\n\n/**\n * Cross-process single-poller lock for a Telegram bot token.\n *\n * Telegram allows exactly one `getUpdates` consumer per token; two wstack\n * instances (TUI + WebUI, or two projects) polling the same token fight each\n * other and every cycle returns HTTP 409. This lock elects one poller: the\n * holder writes a heartbeat to a lock file under `~/.wrongstack/telegram/`,\n * other instances stand by and take over when the heartbeat goes stale or\n * the file disappears.\n */\n\ninterface LockFilePayload {\n /** Unique per PollLock instance \u2014 `pid` alone can't distinguish two locks in one process. */\n id: string;\n pid: number;\n acquiredAt: number;\n heartbeatAt: number;\n}\n\nexport interface PollLockOptions {\n log?: Logger | undefined;\n /** How often the holder refreshes its heartbeat. Default: 15s. */\n heartbeatMs?: number | undefined;\n /** A lock whose heartbeat is older than this is considered stale. Default: 45s. */\n staleMs?: number | undefined;\n}\n\n/** Lock file path for a bot token. The token itself never appears in the path. */\nexport function lockPathForToken(token: string, globalRoot = wstackGlobalRoot()): string {\n const hash = createHash('sha256').update(token).digest('hex').slice(0, 12);\n return join(globalRoot, 'telegram', `poll-${hash}.lock`);\n}\n\nexport class PollLock {\n private readonly id = `${process.pid}:${randomUUID()}`;\n private readonly heartbeatMs: number;\n private readonly staleMs: number;\n private readonly log?: Logger | undefined;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private _held = false;\n\n /** Invoked when the lock is stolen by another instance while held. */\n onLost?: (() => void) | undefined;\n\n constructor(\n readonly lockPath: string,\n opts?: PollLockOptions,\n ) {\n this.heartbeatMs = opts?.heartbeatMs ?? 15_000;\n this.staleMs = opts?.staleMs ?? 45_000;\n this.log = opts?.log;\n }\n\n get held(): boolean {\n return this._held;\n }\n\n /**\n * Try to acquire the lock. Returns true when this instance is now (or was\n * already) the holder. Safe to call repeatedly from a standby retry loop.\n */\n tryAcquire(): boolean {\n if (this._held) return true;\n\n const existing = this.readLock();\n if (existing && !this.isStale(existing)) return false;\n\n try {\n mkdirSync(dirname(this.lockPath), { recursive: true });\n // Remove any stale or corrupt file first, then create exclusively: when\n // two standby instances race for a stale lock, `wx` makes exactly one win.\n try {\n unlinkSync(this.lockPath);\n } catch {\n // Nothing to remove, or a competing instance already removed it.\n }\n const now = Date.now();\n const payload: LockFilePayload = {\n id: this.id,\n pid: process.pid,\n acquiredAt: now,\n heartbeatAt: now,\n };\n writeFileSync(this.lockPath, JSON.stringify(payload), { flag: 'wx' });\n } catch {\n return false; // Lost the race or the directory is unwritable.\n }\n\n this._held = true;\n this.startHeartbeat();\n return true;\n }\n\n /** Release the lock and stop the heartbeat. Idempotent. */\n release(): void {\n this.stopHeartbeat();\n if (!this._held) return;\n this._held = false;\n try {\n if (this.readLock()?.id === this.id) unlinkSync(this.lockPath);\n } catch {\n // Best effort \u2014 a stale file is reclaimed via the staleness check anyway.\n }\n }\n\n // ------------------------------------------------------------------\n // Internals\n // ------------------------------------------------------------------\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => this.heartbeatTick(), this.heartbeatMs);\n this.heartbeatTimer.unref?.();\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private heartbeatTick(): void {\n const current = this.readLock();\n if (!current || current.id !== this.id) {\n // Another instance stole the lock (e.g. this process was suspended past\n // the staleness window). Stop claiming it and notify the owner.\n this._held = false;\n this.stopHeartbeat();\n this.log?.warn('Telegram: poll lock was taken over by another instance.');\n this.onLost?.();\n return;\n }\n try {\n const payload: LockFilePayload = { ...current, heartbeatAt: Date.now() };\n // Write via temp + rename so a reader never sees a half-written file.\n const tmp = `${this.lockPath}.${process.pid}.tmp`;\n writeFileSync(tmp, JSON.stringify(payload));\n renameSync(tmp, this.lockPath);\n } catch (err) {\n this.log?.debug(`Telegram: poll lock heartbeat write failed: ${err}`);\n }\n }\n\n private readLock(): LockFilePayload | null {\n try {\n const raw = readFileSync(this.lockPath, 'utf8');\n const parsed = JSON.parse(raw) as LockFilePayload;\n if (typeof parsed.id !== 'string' || typeof parsed.pid !== 'number') return null;\n return parsed;\n } catch {\n return null; // Missing or corrupt \u2014 treated as stale/absent.\n }\n }\n\n private isStale(payload: LockFilePayload): boolean {\n if (Date.now() - payload.heartbeatAt > this.staleMs) return true;\n return !this.isPidAlive(payload.pid);\n }\n\n private isPidAlive(pid: number): boolean {\n if (pid === process.pid) return true;\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM means the process exists but belongs to another user.\n return (err as NodeJS.ErrnoException).code === 'EPERM';\n }\n }\n}\n", "import { createHash } from 'node:crypto';\nimport {\n closeSync,\n fsyncSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { wstackGlobalRoot } from '@wrongstack/core/utils';\n\n/**\n * Offset file path for a bot token. The token itself never appears in the path.\n * Uses the same hash convention as PollLock so both are discoverable together.\n */\nexport function offsetPathForToken(token: string, globalRoot = wstackGlobalRoot()): string {\n const hash = createHash('sha256').update(token).digest('hex').slice(0, 12);\n return join(globalRoot, 'telegram', `offset-${hash}.json`);\n}\n\n/**\n * Typed offset-cursor persistence for Telegram bot polling.\n *\n * Writes are atomic (temp file + rename) so a crash mid-write never leaves a\n * corrupt or incomplete file. Reads handle missing, empty, and malformed files\n * transparently \u2014 the caller always gets a valid non-negative number or null.\n */\nexport interface OffsetStoreOptions {\n /** Bot token \u2014 derives a token-scoped default path. Never persisted. */\n token?: string | undefined;\n /**\n * Explicit file path override. Takes precedence over token derivation. An\n * empty string is treated as \"no path\", which disables persistence.\n */\n path?: string | undefined;\n /** Base directory for token-scoped derivation (defaults to wstackGlobalRoot). */\n globalRoot?: string | undefined;\n}\n\nexport class OffsetStore {\n private readonly path: string;\n\n constructor(opts: OffsetStoreOptions = {}) {\n if (opts.path !== undefined) {\n this.path = opts.path;\n } else if (opts.token) {\n this.path = offsetPathForToken(opts.token, opts.globalRoot);\n } else {\n this.path = '';\n }\n }\n\n /** The derived path for diagnostics. */\n get storePath(): string {\n return this.path;\n }\n\n /**\n * Read the persisted offset. Returns null when the file is missing, empty,\n * or contains a value that is not a valid non-negative integer.\n */\n read(): number | null {\n if (!this.path) return null;\n\n let raw: string;\n try {\n raw = readFileSync(this.path, 'utf8').trim();\n } catch {\n return null;\n }\n\n if (raw.length === 0) return null;\n\n try {\n const parsed = JSON.parse(raw);\n if (\n typeof parsed !== 'number' ||\n !Number.isFinite(parsed) ||\n parsed < 0 ||\n parsed % 1 !== 0\n ) {\n return null;\n }\n return parsed;\n } catch {\n return null;\n }\n }\n\n /**\n * Persist an offset value using an atomic write (temp file + rename).\n * Creates the parent directory on first call.\n */\n write(offset: number): void {\n if (!this.path || offset < 0) return;\n\n mkdirSync(dirname(this.path), { recursive: true });\n\n const tmp = `${this.path}.${process.pid}.tmp`;\n // Write to a temp file, fsync it to durable storage, then atomically rename\n // over the target. fsync before rename guarantees the bytes are on disk on\n // both POSIX and Windows before the rename makes them visible.\n const fd = openSync(tmp, 'w');\n try {\n writeSync(fd, JSON.stringify(offset));\n fsyncSync(fd);\n } finally {\n closeSync(fd);\n }\n try {\n renameSync(tmp, this.path);\n } catch {\n // If rename fails (e.g. cross-device on some setups), clean up the temp\n // file so we don't leak it. The caller can retry on the next poll cycle.\n try {\n unlinkSync(tmp);\n } catch {\n // Temp file removal is best-effort.\n }\n }\n }\n}\n", "import { DefaultSecretScrubber } from '@wrongstack/core/security';\nimport { ToolValidationError } from '@wrongstack/core/types';\nimport { redactSecrets } from '../redact.js';\n\nexport type TelegramChatId = string | number;\n\n/**\n * Narrow capability for creating a Telegram approval request. Unlike the\n * generic `net.outbound` capability, this must be granted explicitly to a\n * subagent before its auto-permission approval tool becomes available.\n */\nexport const TELEGRAM_APPROVAL_CAPABILITY = 'net.outbound.telegram.approval' as const;\n\nexport interface TelegramOutboundTargetPolicy {\n /** Paired/default chat. It is always an allowed outbound target when set. */\n getDefaultChatId(): TelegramChatId | undefined;\n /** Additional explicitly trusted outbound targets, resolved at call time. */\n getAllowedOutboundChatIds?(): readonly TelegramChatId[];\n}\n\nconst secretScrubber = new DefaultSecretScrubber();\nconst RAW_TELEGRAM_BOT_TOKEN = /(?<![A-Za-z0-9])\\d{5,15}:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g;\n\nfunction normalizeChatId(value: TelegramChatId): string {\n return String(value).trim();\n}\n\n/**\n * Resolve an outbound target against the paired chat plus the explicit\n * outbound allowlist. This must run before any Telegram API call.\n */\nexport function resolveTelegramOutboundTarget(\n requestedChatId: TelegramChatId | undefined,\n policy: TelegramOutboundTargetPolicy,\n): TelegramChatId {\n const defaultChatId = policy.getDefaultChatId();\n const target = requestedChatId ?? defaultChatId;\n if (target === undefined || normalizeChatId(target) === '') {\n throw new ToolValidationError({\n field: 'chat_id',\n message:\n 'No chat_id provided and no allowed Telegram target is configured. Pair notifyChatId or configure allowedOutboundChats.',\n });\n }\n\n const allowed = new Set<string>();\n if (defaultChatId !== undefined && normalizeChatId(defaultChatId) !== '') {\n allowed.add(normalizeChatId(defaultChatId));\n }\n for (const chatId of policy.getAllowedOutboundChatIds?.() ?? []) {\n const normalized = normalizeChatId(chatId);\n if (normalized !== '') allowed.add(normalized);\n }\n\n if (!allowed.has(normalizeChatId(target))) {\n throw new ToolValidationError({\n field: 'chat_id',\n message:\n 'Telegram outbound target is not paired or included in allowedOutboundChats.',\n });\n }\n\n return typeof target === 'string' ? target.trim() : target;\n}\n\n/**\n * Scrub outbound text with the shared core credential detector, then retain\n * Telegram's legacy flag/env redaction for labelled secrets the core patterns\n * intentionally do not classify by value alone.\n */\nexport function scrubTelegramOutboundText(text: string): string {\n const shared = secretScrubber.scrub(text);\n const withoutBareBotTokens = shared.replace(\n RAW_TELEGRAM_BOT_TOKEN,\n '[REDACTED:telegram_bot_token]',\n );\n return redactSecrets(withoutBareBotTokens);\n}\n", "import type { PluginAPI } from '@wrongstack/core/plugin';\nimport type { SlashCommand } from '@wrongstack/core/types';\nimport { expectDefined } from '@wrongstack/core/utils';\nimport { type TelegramBot, truncateForTelegram } from '../bot.js';\nimport type { TelegramPluginConfig } from '../config.js';\nimport {\n resolveTelegramOutboundTarget,\n scrubTelegramOutboundText,\n type TelegramOutboundTargetPolicy,\n} from '../security/outbound.js';\nimport type { TelegramBotOutbound } from '../bot-queue.js';\n\n// ---------------------------------------------------------------------------\n// /telegram-health\n// ---------------------------------------------------------------------------\n\nexport function tgHealthCommand(bot: TelegramBot, cfg: TelegramPluginConfig): SlashCommand {\n return {\n name: 'telegram-health',\n aliases: ['telegram', 'tgstat', 'tgs'],\n description: 'Show Telegram bot connection health and config',\n help: `Usage: /telegram-health\nAliases: /telegram, /tgstat, /tgs\n\nShows whether the bot is connected, its username, polling interval,\nallowlist health, and notification settings.`,\n async run(_args, _ctx) {\n const health = await bot.health();\n const lines = [\n '\u2550\u2550\u2550 Telegram Plugin Status \u2550\u2550\u2550',\n '',\n `Bot: ${health.ok ? `\u2705 @${health.username ?? 'connected'}` : `\u274C ${health.error ?? 'offline'}`}`,\n `Running: ${bot.running ? 'yes' : 'no'}`,\n `Started: ${bot.startedAt ? new Date(bot.startedAt).toLocaleTimeString() : 'N/A'}`,\n `Poll: every ${cfg.pollIntervalSec ?? 2}s`,\n `Allowed: ${(cfg.allowedUsers?.length ?? 0) > 0 ? `${cfg.allowedUsers?.length} users` : 'everyone (users)'} / ${(cfg.allowedChats?.length ?? 0) > 0 ? `${cfg.allowedChats?.length} chats` : 'everyone (chats)'}`,\n `Notify: sessionEnd=${cfg.notifyOnSessionEnd ?? false}, longTool=${cfg.longToolThresholdMs ? `${cfg.longToolThresholdMs}ms` : 'off'}`,\n ];\n\n return { message: lines.join('\\n') };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// /telegram:send\n// ---------------------------------------------------------------------------\n\ninterface TelegramSlashSendPolicy extends TelegramOutboundTargetPolicy {\n getMaxMessageLength?(): number;\n}\n\nexport function tgSendCommand(\n bot: TelegramBot,\n policyOrDefault: TelegramSlashSendPolicy | string | number | undefined,\n outbound?: TelegramBotOutbound,\n): SlashCommand {\n const policy: TelegramSlashSendPolicy =\n typeof policyOrDefault === 'object' && policyOrDefault !== null\n ? policyOrDefault\n : { getDefaultChatId: () => policyOrDefault };\n\n return {\n name: 'send',\n description: 'Send a message to a Telegram chat',\n help: `Usage: /telegram:send [chat_id] <message>\n\nSend a message to a Telegram chat.\n- First argument (optional): chat or user ID. Uses notifyChatId from config when omitted.\n- Everything else: the message text.\n\nExamples:\n /telegram:send 123456789 Build completed successfully \u2713\n /telegram:send Deploy finished \u2014 check staging`,\n async run(args, _ctx) {\n if (!args.trim()) {\n return { message: 'Usage: /telegram:send [chat_id] <message>' };\n }\n\n let requestedChatId: string | number | undefined;\n let text: string;\n\n // First token might be a numeric chat_id. Telegram group/supergroup IDs\n // are negative, so accept an optional leading minus sign.\n const parts = args.trim().split(/\\s+/);\n const maybeId = parts[0];\n if (/^-?\\d+$/.test(expectDefined(maybeId)) && parts.length > 1) {\n requestedChatId = expectDefined(maybeId);\n text = parts.slice(1).join(' ');\n } else {\n text = args.trim();\n }\n\n try {\n const chatId = resolveTelegramOutboundTarget(requestedChatId, policy);\n const scrubbed = scrubTelegramOutboundText(text);\n const truncated = truncateForTelegram(scrubbed, policy.getMaxMessageLength?.() ?? 4000);\n if (outbound) {\n const res = await outbound.sendManual(chatId, truncated);\n return {\n message: `\u2705 Message sent to ${chatId} (msg_id=${res.result?.message_id ?? '?'})`,\n };\n }\n const res = await bot.sendMessage(chatId, truncated);\n return {\n message: `\u2705 Message sent to ${chatId} (msg_id=${res.result?.message_id ?? '?'})`,\n };\n } catch (err) {\n return { message: `\u274C Failed to send: ${(err as Error).message}` };\n }\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// /telegram:chatid\n// ---------------------------------------------------------------------------\n\nexport function tgChatIdCommand(defaultChatId?: string | number): SlashCommand {\n const chatIdStr = defaultChatId ? String(defaultChatId) : null;\n return {\n name: 'chatid',\n description: 'Show the configured default chat ID',\n help: `Usage: /telegram:chatid\n\nShows the current default notifyChatId used for notifications\nand the \\`telegram_send\\` tool when no chat_id is specified.`,\n async run(_args, _ctx) {\n if (chatIdStr) {\n return { message: `Configured notifyChatId: ${chatIdStr}` };\n }\n return {\n message:\n 'No notifyChatId configured. Set it in the plugin config or pass chat_id explicitly to telegram_send.',\n };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Register all\n// ---------------------------------------------------------------------------\n\nexport function registerSlashCommands(\n api: PluginAPI,\n bot: TelegramBot,\n cfg: TelegramPluginConfig,\n): string[] {\n const cmds = [\n tgHealthCommand(bot, cfg),\n tgSendCommand(bot, {\n getDefaultChatId: () => cfg.notifyChatId,\n getAllowedOutboundChatIds: () => cfg.allowedOutboundChats ?? [],\n getMaxMessageLength: () => cfg.maxMessageLength ?? 4000,\n }),\n tgChatIdCommand(cfg.notifyChatId),\n ];\n for (const cmd of cmds) api.slashCommands.register(cmd);\n return cmds.map((c) => c.name);\n}\n", "import { randomUUID } from 'node:crypto';\nimport type { Logger, Tool } from '@wrongstack/core/types';\nimport type { TelegramBot } from '../bot.js';\nimport { truncateForTelegram } from '../bot.js';\nimport {\n resolveTelegramOutboundTarget,\n scrubTelegramOutboundText,\n TELEGRAM_APPROVAL_CAPABILITY,\n type TelegramChatId,\n} from '../security/outbound.js';\n\ninterface TelegramApproveInput {\n /** Short label for what's being approved (\u2264 60 chars). Shown as the prompt heading. */\n prompt: string;\n /** Optional details (\u2264 1000 chars). Shown under the heading. */\n details?: string | undefined;\n /** Chat to post the prompt to. Falls back to notifyChatId. */\n chat_id?: string | number | undefined;\n /** How long to wait for a button press before auto-denying. Default 60s, max 600s. */\n timeout_ms?: number | undefined;\n}\n\ninterface TelegramApproveOutput {\n approved: boolean;\n /** Immutable Telegram user ID; absent for timeout, shutdown, or rejection. */\n user_id?: number | undefined;\n /** Human-readable username/first name; never used for authorization. */\n display_name: string;\n /** Backward-compatible alias for display_name. */\n from: string;\n prompt_message_id?: number | undefined;\n}\n\n/**\n * Post a yes/no inline-keyboard prompt to a chat and block until the user\n * taps a button (or until `timeout_ms` elapses, in which case the call\n * auto-denies). Useful when the agent wants explicit approval before\n * continuing and the user is on their phone rather than the TUI.\n *\n * The agent calls this tool directly. It does not replace the host-level\n * `permission: 'confirm'` flow \u2014 for that, see the future B4 work.\n *\n * Permission: `auto` (NOT `confirm`). This is intentional \u2014 the tool's\n * purpose IS to obtain user approval; gating it behind another host-level\n * confirm dialog would be circular and would block the agent in\n * headless mode. The user-side approval (Telegram button press) is\n * the only confirm gate. The 600 s tool `timeoutMs` ceiling is the\n * safety net for the case where the user never responds.\n */\nexport function makeTelegramApproveTool(opts: {\n bot: TelegramBot;\n /** Paired/default target, resolved on every call for live config updates. */\n getDefaultChatId(): TelegramChatId | undefined;\n /** Additional trusted targets, resolved on every call for live config updates. */\n getAllowedOutboundChatIds?(): readonly TelegramChatId[];\n /** Immutable Telegram user IDs permitted to resolve an approval. */\n getAllowedUserIds?(): readonly TelegramChatId[];\n /**\n * Group approvals stay denied unless both this and explicit user IDs are\n * configured. Resolved on every call for live config updates.\n */\n getAllowGroupApprovals?(): boolean;\n maxMessageLength: number;\n log: Logger;\n}): Tool<TelegramApproveInput, TelegramApproveOutput> {\n return {\n name: 'telegram_approve',\n description:\n 'Post a scrubbed yes/no prompt only to the paired Telegram chat or an explicitly allowed outbound chat, then wait for a button press. Returns approval state plus immutable user_id and display_name; false means timeout, rejection, or explicit deny. This narrow capability requests remote approval but does not itself authorize or perform the proposed operation.',\n usageHint:\n 'telegram_approve(prompt: \"Delete build artifacts?\", details: \"Frees 2.3 GB. Cannot be undone.\", timeout_ms: 60000)',\n category: 'Telegram',\n inputSchema: {\n type: 'object',\n properties: {\n prompt: {\n type: 'string',\n maxLength: 200,\n description: 'Short label for what is being approved. Shown as the prompt heading.',\n },\n details: {\n type: 'string',\n maxLength: 1000,\n description: 'Optional context under the heading.',\n },\n chat_id: {\n oneOf: [{ type: 'string' }, { type: 'integer' }],\n description: 'Chat to post the prompt to. Uses the plugin default when omitted.',\n },\n timeout_ms: {\n type: 'integer',\n minimum: 1000,\n maximum: 600_000,\n description:\n 'How long to wait before auto-denying. Default 60 000 ms, max 600 000 ms (10 min).',\n },\n },\n required: ['prompt'],\n },\n permission: 'auto',\n mutating: true,\n riskTier: 'standard',\n capabilities: [TELEGRAM_APPROVAL_CAPABILITY],\n timeoutMs: 610_000,\n async execute(input, ctx, toolOpts) {\n const chatId = resolveTelegramOutboundTarget(input.chat_id, opts);\n const timeoutMs = Math.min(Math.max(input.timeout_ms ?? 60_000, 1000), 600_000);\n const configuredUserIds = opts.getAllowedUserIds?.().map(String) ?? [];\n const isGroup = String(chatId).startsWith('-');\n if (isGroup && (opts.getAllowGroupApprovals?.() !== true || configuredUserIds.length === 0)) {\n throw new Error('Telegram group approvals require explicit per-user configuration.');\n }\n const expectedUserIds = configuredUserIds.length > 0 ? configuredUserIds : [String(chatId)];\n\n // Stable request identity shared by the yes/no callback actions.\n const requestId = randomUUID().slice(0, 16);\n const yesKey = `approve:${requestId}:yes`;\n const noKey = `approve:${requestId}:no`;\n\n // Scrub every user-controlled outbound field before truncation so raw\n // credentials never reach Telegram, even inside an approval prompt.\n const prompt = scrubTelegramOutboundText(input.prompt);\n const details = input.details\n ? truncateForTelegram(scrubTelegramOutboundText(input.details), 800)\n : undefined;\n const heading = `\u26A0\uFE0F ${prompt}`;\n const detailsLine = details ? `\\n\\n${details}` : '';\n const text = `${heading}${detailsLine}\\n\\n_Reply by tapping a button. Auto-denies in ${Math.round(timeoutMs / 1000)}s._`;\n\n opts.log.info(`telegram_approve \u2192 chat_id=${chatId} (${prompt.length} prompt chars)`);\n\n // Register before sending so an immediate callback cannot beat waiter\n // creation. The same request owns its timer through send, bind, and\n // terminal settlement.\n const approval = opts.bot.awaitApproval({\n requestId,\n sessionId: ctx?.session.id ?? 'unknown-session',\n expectedChatId: chatId,\n expectedUserIds,\n allowGroup: isGroup && opts.getAllowGroupApprovals?.() === true,\n expiresAt: Date.now() + timeoutMs,\n signal: toolOpts?.signal,\n });\n\n let promptMessageId: number | undefined;\n try {\n const sent = await opts.bot.sendMessageWithKeyboard(chatId, text, [\n { text: '\u2705 Approve', callback_data: yesKey },\n { text: '\u274C Deny', callback_data: noKey },\n ], toolOpts?.signal);\n promptMessageId = sent.result?.message_id;\n if (promptMessageId === undefined) {\n throw new Error('Telegram approval prompt response did not include a message ID.');\n }\n if (!opts.bot.bindApprovalPrompt(requestId, promptMessageId)) {\n throw new Error('Telegram approval request ended before its prompt could be bound.');\n }\n } catch (err) {\n opts.bot.cancelApproval(requestId, 'send-failed');\n await approval;\n opts.log.debug(`telegram_approve send failed: ${(err as Error).message}`);\n throw err;\n }\n\n const result = await approval;\n return {\n approved: result.approved,\n user_id: result.fromUserId,\n display_name: result.fromUser,\n from: result.fromUser,\n prompt_message_id: promptMessageId,\n };\n },\n };\n}\n", "// ---------------------------------------------------------------------------\n// Bounded outbound send queue with per-chat backpressure.\n//\n// Replaces ad-hoc fire-and-forget Promise chains at the notification\n// call sites (session.ended, tool.executed, delegate.completed) so a flood\n// of events cannot create unbounded in-flight promises, and so one slow\n// chat cannot stall messages destined for other chats.\n//\n// Contract:\n// - Manual sends (telegram_send tool, /telegram:send) are user-triggered;\n// enqueue() either sends synchronously or rejects with a clear error.\n// They are never silently dropped or coalesced.\n// - Automatic notifications are best-effort: when a chat's queue is full,\n// the oldest pending entry is dropped (counted in stats) and a debug\n// log records the drop. The newest entry is enqueued.\n// - Per-chat ordering: messages to the same chatId are serialised in the\n// order enqueue() was called for them.\n// - Cross-chat concurrency: independent chats are dispatched in parallel.\n// - Backpressure: when a single chat exceeds `maxPerChat`, the oldest\n// pending notification entry is dropped (logged + counted) to keep the\n// queue bounded. Manual entries instead reject with an overflow error\n// so the caller never gets a silent failure.\n// - Drain on stop(): pending entries are flushed best-effort; subsequent\n// enqueues after stop() reject with a clear error.\n// ---------------------------------------------------------------------------\n\nimport type { Logger } from '@wrongstack/core/types';\n\nexport type OutboundKind = 'notification' | 'manual';\n\nexport interface OutboundEntry {\n readonly chatId: string | number;\n readonly text: string;\n /** Manual = user-triggered (never dropped). Notification = best-effort. */\n readonly kind: OutboundKind;\n}\n\n/**\n * Internal entry shape. The `id` is assigned **once** at enqueue time and\n * reused for every resolver lookup (run/stop). Crucially, it must NOT be\n * recomputed from a mutable counter at completion time \u2014 if it were, two\n * interleaved sends to the same chat would diverge in their keys and one\n * would hang forever (the resolver would never be found).\n */\ninterface InternalEntry extends OutboundEntry {\n readonly id: number;\n}\n\nexport interface OutboundQueueOptions {\n /** Bound per chat; default 32. Older pending entries are dropped on overflow. */\n readonly maxPerChat?: number | undefined;\n /** Bound for the total concurrent API calls; default 4. */\n readonly maxConcurrency?: number | undefined;\n /**\n * Producer of the actual HTTP call. The queue invokes this exactly once\n * per dequeued entry and propagates the resolved value (or rejection) to\n * the original enqueue caller. Keeping this as an injected function lets\n * the queue own ordering/backpressure while tests swap a fake transport.\n */\n readonly send: (chatId: string | number, text: string) => Promise<unknown>;\n readonly log?: Logger | undefined;\n}\n\nexport interface OutboundQueueStats {\n readonly enqueued: number;\n readonly sent: number;\n readonly dropped: number;\n readonly failed: number;\n readonly inflight: number;\n /** Accepted entries not yet settled, including the in-flight sends. */\n readonly pending: number;\n}\n\n/** Per-chat serial queue state. */\ninterface ChatLane {\n pending: InternalEntry[];\n running: boolean;\n}\n\nconst DEFAULT_MAX_PER_CHAT = 32;\nconst DEFAULT_MAX_CONCURRENCY = 4;\n\nexport class OutboundQueue {\n readonly #opts: {\n maxPerChat: number;\n maxConcurrency: number;\n send: (chatId: string | number, text: string) => Promise<unknown>;\n log: Logger | undefined;\n };\n readonly #lanes = new Map<string, ChatLane>();\n #active = 0;\n #notificationScheduleQueued = false;\n #stopped = false;\n #nextId = 0;\n #enqueued = 0;\n #sent = 0;\n #dropped = 0;\n #failed = 0;\n #resolvers = new Map<\n number,\n { resolve: (value: unknown) => void; reject: (err: unknown) => void }\n >();\n\n constructor(opts: OutboundQueueOptions) {\n const maxPerChat = opts.maxPerChat ?? DEFAULT_MAX_PER_CHAT;\n const maxConcurrency = opts.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY;\n this.#opts = {\n maxPerChat,\n maxConcurrency,\n send: opts.send,\n log: opts.log,\n };\n }\n\n /**\n * Enqueue an outbound send. For manual entries the returned promise\n * resolves with the send result (or rejects with the send error / the\n * overflow error). For notification entries the returned promise resolves\n * as soon as the queue accepts the entry, so callers don't block;\n * downstream drain failures are logged and counted but do not propagate.\n */\n enqueue(entry: OutboundEntry): Promise<unknown> {\n if (this.#stopped) {\n return Promise.reject(new Error('Outbound queue is stopped'));\n }\n const internal: InternalEntry = { ...entry, id: this.#mintId() };\n const key = String(entry.chatId);\n let lane = this.#lanes.get(key);\n if (!lane) {\n lane = { pending: [], running: false };\n this.#lanes.set(key, lane);\n }\n if (entry.kind === 'notification') {\n if (lane.pending.length >= this.#opts.maxPerChat) {\n const dropped = lane.pending.shift();\n if (dropped) {\n this.#dropped += 1;\n const droppedResolver = this.#resolvers.get(dropped.id);\n if (droppedResolver) {\n this.#resolvers.delete(dropped.id);\n // Settle the orphaned promise so the fire-and-forget caller\n // (which already moved on past the enqueue promise) doesn't\n // leak a permanently-pending resolver.\n droppedResolver.resolve(undefined);\n }\n this.#opts.log?.debug(\n `Telegram outbound queue dropped a notification for chat ${dropped.chatId} (per-chat limit ${this.#opts.maxPerChat})`,\n );\n }\n }\n } else if (lane.pending.length + (lane.running ? 1 : 0) >= this.#opts.maxPerChat) {\n // Manual overflow: surface a real error instead of silently dropping.\n // Per the P1.4 acceptance criterion, manual sends are never\n // silently dropped or coalesced.\n // Counts both pending and in-flight entries since `running` means one\n // entry has been dequeued from pending but is still being sent.\n return Promise.reject(\n new Error(\n `Telegram outbound queue per-chat limit reached for chat ${entry.chatId} (max ${this.#opts.maxPerChat})`,\n ),\n );\n }\n lane.pending.push(internal);\n this.#enqueued += 1;\n\n if (entry.kind === 'notification') {\n // Notification delivery is best-effort. Its promise represents queue\n // acceptance, not transport completion, so event handlers never block\n // behind a slow Telegram request. Batch notifications enqueued in the\n // same turn before dispatching; this lets the bounded queue consistently\n // drop the oldest entries during a burst.\n this.#scheduleNotifications();\n return Promise.resolve(undefined);\n }\n\n return new Promise<unknown>((resolve, reject) => {\n this.#resolvers.set(internal.id, { resolve, reject });\n this.#schedule();\n });\n }\n\n /** Stats snapshot for `/telegram-health` and the P3.1 metrics surface. */\n stats(): OutboundQueueStats {\n let pending = this.#active;\n for (const lane of this.#lanes.values()) pending += lane.pending.length;\n return {\n enqueued: this.#enqueued,\n sent: this.#sent,\n dropped: this.#dropped,\n failed: this.#failed,\n inflight: this.#active,\n pending,\n };\n }\n\n /**\n * Stop accepting new entries. Returns a promise that resolves once all\n * currently in-flight sends have settled and every per-chat lane is\n * empty. Pending entries are rejected so their callers don't hang.\n */\n async stop(): Promise<void> {\n this.#stopped = true;\n for (const lane of this.#lanes.values()) {\n for (const entry of lane.pending.splice(0)) {\n this.#dropped += 1;\n const resolver = this.#resolvers.get(entry.id);\n if (resolver) {\n this.#resolvers.delete(entry.id);\n resolver.reject(new Error('Outbound queue stopped before send'));\n }\n this.#opts.log?.debug(\n `Telegram outbound queue stopped, dropped pending ${entry.kind} for chat ${entry.chatId}`,\n );\n }\n }\n while (this.#active > 0) {\n await new Promise((r) => setTimeout(r, 5));\n }\n }\n\n #mintId(): number {\n this.#nextId += 1;\n return this.#nextId;\n }\n\n #scheduleNotifications(): void {\n if (this.#notificationScheduleQueued) return;\n this.#notificationScheduleQueued = true;\n // Two microtask hops preserve immediate acceptance while allowing both\n // sequential awaits and Promise.all acceptance checks to settle before\n // transport work begins. Synchronous notification bursts are therefore\n // bounded as one batch instead of leaking the first entry in-flight.\n queueMicrotask(() => {\n queueMicrotask(() => {\n this.#notificationScheduleQueued = false;\n this.#schedule();\n });\n });\n }\n\n #schedule(): void {\n if (this.#stopped) return;\n // Run up to maxConcurrency inflight across all lanes. Each lane\n // serialises itself so two entries to the same chat never run together.\n while (this.#active < this.#opts.maxConcurrency) {\n const entry = this.#nextReady();\n if (!entry) return;\n this.#active += 1;\n void this.#run(entry);\n }\n }\n\n #nextReady(): InternalEntry | undefined {\n // Prefer lanes whose running=false so each chat progresses in FIFO order\n // even when the global concurrency cap is lower than the lane count.\n for (const lane of this.#lanes.values()) {\n if (!lane.running && lane.pending.length > 0) {\n lane.running = true;\n return lane.pending.shift();\n }\n }\n return undefined;\n }\n\n async #run(entry: InternalEntry): Promise<void> {\n const key = String(entry.chatId);\n const lane = this.#lanes.get(key);\n if (!lane) {\n // The lane vanished (queue stopped mid-flight); settle the resolver so\n // the caller doesn't hang on a promise that will never see completion.\n const resolver = this.#resolvers.get(entry.id);\n if (resolver) {\n this.#resolvers.delete(entry.id);\n resolver.resolve(undefined);\n }\n this.#active -= 1;\n return;\n }\n try {\n const result = await this.#opts.send(entry.chatId, entry.text);\n this.#sent += 1;\n const resolver = this.#resolvers.get(entry.id);\n if (resolver) {\n this.#resolvers.delete(entry.id);\n resolver.resolve(result);\n }\n } catch (err) {\n this.#failed += 1;\n const resolver = this.#resolvers.get(entry.id);\n if (resolver) {\n this.#resolvers.delete(entry.id);\n // Only manual entries retain a completion resolver. Notification\n // promises settle on acceptance, before transport work begins.\n resolver.reject(err);\n } else if (entry.kind === 'notification') {\n // Best-effort notification failures are observable through logs and\n // stats without becoming unhandled promise rejections at call sites.\n this.#opts.log?.debug(\n `Telegram outbound queue notification failed for chat ${entry.chatId}: ${(err as Error).message}`,\n );\n }\n } finally {\n this.#active -= 1;\n lane.running = false;\n this.#schedule();\n }\n }\n}\n", "// ---------------------------------------------------------------------------\n// Per-chat token bucket rate limiter for Telegram outbound sends.\n//\n// Telegram enforces per-chat rate limits:\n// - Groups: ~20 messages per minute (\u2248 0.33 msg/s)\n// - Private chats: ~30 messages per second\n//\n// Instead of hardcoding chat-type limits (which would require an API call\n// per chat to discover the type), this limiter exposes a configurable\n// tokens-per-second and burst cap. The caller (OutboundQueue) decides\n// the policy for each chat based on known chat type.\n//\n// Each chat gets its own token bucket. When a send is attempted and no\n// tokens are available, the caller waits (via waitForToken) until a token\n// refills. The wait is bounded by an optional timeout to prevent head-of-line\n// blocking when a slow chat stalls the queue.\n//\n// Thread safety: the bucket state is guarded by the calling pattern \u2014\n// OutboundQueue serializes per-chat sends (single ChatLane runner), so\n// concurrent access to the same bucket never happens. Cross-chat buckets\n// are independent and need no coordination.\n// ---------------------------------------------------------------------------\n\nexport interface RateLimiterOptions {\n /** Tokens per second (refill rate). Default: 0.33 (\u224820/min for groups). */\n tokensPerSecond?: number | undefined;\n /** Maximum burst size (bucket capacity). Default: 4. */\n burst?: number | undefined;\n}\n\nexport interface TokenBucket {\n /** Wait until a token is available, respecting the optional timeout. */\n waitForToken(timeoutMs?: number | undefined): Promise<void>;\n /** Current fill level (for diagnostics). */\n fill(): number;\n}\n\n/**\n * Create a token bucket for a single chat.\n *\n * The bucket starts full (`burst` tokens). Every `waitForToken` call\n * consumes one token if available; otherwise it waits until a token\n * is refilled. Refills happen at `tokensPerSecond` rate, capped at\n * `burst`.\n *\n * @example\n * ```ts\n * const bucket = createTokenBucket({ tokensPerSecond: 0.33, burst: 4 });\n * await bucket.waitForToken(5_000); // waits up to 5s for a slot\n * bot.sendMessage(chatId, text);\n * ```\n */\nexport function createTokenBucket(opts?: RateLimiterOptions): TokenBucket {\n const tokensPerSecond = opts?.tokensPerSecond ?? 0.33;\n const burst = opts?.burst ?? 4;\n const refillIntervalMs = 1000 / tokensPerSecond;\n\n let tokens = burst;\n let lastRefill = Date.now();\n\n return { waitForToken, fill };\n\n async function waitForToken(timeoutMs?: number | undefined): Promise<void> {\n const deadline = timeoutMs !== undefined ? Date.now() + timeoutMs : Infinity;\n\n while (true) {\n refill();\n\n if (tokens >= 1) {\n tokens -= 1;\n return;\n }\n\n const now = Date.now();\n if (now >= deadline) {\n return; // Timeout \u2014 caller should proceed or skip\n }\n\n // Wait until the next refill tick or the deadline, whichever is sooner.\n const nextRefill = lastRefill + refillIntervalMs;\n const delay = Math.min(\n Math.max(nextRefill - now, 0),\n deadline - now,\n 5000, // safety cap: never sleep longer than 5s\n );\n\n await sleep(delay);\n }\n }\n\n function refill(): void {\n const now = Date.now();\n const elapsed = now - lastRefill;\n if (elapsed <= 0) return;\n\n const newTokens = (elapsed / 1000) * tokensPerSecond;\n tokens = Math.min(burst, tokens + newTokens);\n lastRefill = now;\n }\n\n function fill(): number {\n refill();\n return tokens;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n", "// ---------------------------------------------------------------------------\n// Telegram outbound queue integration.\n//\n// Provides a TelegramBot helper that routes manual sends (the\n// telegram_send tool, /telegram:send) through the same bounded outbound\n// queue used by automatic notifications, so user-triggered and\n// notification-triggered sends share per-chat ordering and backpressure.\n//\n// Manual entries reject on overflow (caller sees the error), per the P1.4\n// acceptance criterion \"manual sends are never silently dropped\".\n// Notification entries are dropped on overflow per the same criterion.\n//\n// Rate limiting: each chat gets a token bucket that paces sends according\n// to Telegram's per-chat rate limits. The default policy (0.33 tokens/s,\n// burst 4) is conservative for group chats while permitting short notification\n// bursts to drain immediately; private chats use a higher rate (30 tokens/s,\n// burst 5) when the chat type is known.\n// ---------------------------------------------------------------------------\n\nimport type { Logger } from '@wrongstack/core/types';\nimport type { TelegramApiMessage } from './api-client.js';\nimport type { TelegramBot, TelegramBotResponse } from './bot.js';\nimport { OutboundQueue, type OutboundEntry } from './outbound-queue.js';\nimport { createTokenBucket, type TokenBucket } from './rate-limiter.js';\n\nexport interface BotOutboundOptions {\n readonly bot: TelegramBot;\n /** Pass-through logger (defaults to bot's internal logger via the bot's debug hook). */\n readonly log: Logger;\n /** Optional override; defaults to 32 entries per chat. */\n readonly maxPerChat?: number;\n /** Optional override; defaults to 4 concurrent sends. */\n readonly maxConcurrency?: number;\n /**\n * Live getter for tokens per second on the per-chat rate limiter.\n * Default: 0.33 (\u224820 messages/minute, safe for groups).\n * Private chats can safely use 30.\n * Passed as a getter so config hot-reload takes effect on the next new\n * bucket (existing buckets retain their captured parameters for the\n * queue lifetime, which is the intended lifetime of the bot).\n */\n readonly getRateLimitTokensPerSecond?: (() => number) | undefined;\n /**\n * Live getter for maximum burst size on the per-chat rate limiter.\n * Default: 4. Read lazily per new-bucket creation so config\n * hot-reload propagates without queue rebuild.\n */\n readonly getRateLimitBurst?: (() => number) | undefined;\n}\n\nexport class TelegramBotOutbound {\n readonly #queue: OutboundQueue;\n readonly #bot: TelegramBot;\n readonly #log: Logger;\n readonly #buckets = new Map<string, TokenBucket>();\n readonly #getRateTokensPerSecond: () => number;\n readonly #getRateBurst: () => number;\n #stopped = false;\n\n constructor(opts: BotOutboundOptions) {\n this.#bot = opts.bot;\n this.#log = opts.log;\n this.#getRateTokensPerSecond = opts.getRateLimitTokensPerSecond ?? (() => 0.33);\n this.#getRateBurst = opts.getRateLimitBurst ?? (() => 4);\n this.#queue = new OutboundQueue({\n maxPerChat: opts.maxPerChat,\n maxConcurrency: opts.maxConcurrency,\n send: (chatId, text) => this.#rateLimitedSend(chatId, text),\n log: opts.log,\n });\n }\n\n /** Per-chat rate-limited send: waits for a token, then delegates to the bot.\n * Rate-limit values are resolved lazily when a new bucket is created via\n * the constructor getters so a live config change applies to subsequent\n * chats without rebuilding the queue. */\n async #rateLimitedSend(\n chatId: string | number,\n text: string,\n ): Promise<TelegramBotResponse<TelegramApiMessage>> {\n const key = String(chatId);\n let bucket = this.#buckets.get(key);\n if (!bucket) {\n bucket = createTokenBucket({\n tokensPerSecond: this.#getRateTokensPerSecond(),\n burst: this.#getRateBurst(),\n });\n this.#buckets.set(key, bucket);\n }\n\n // Wait up to 5s for a token slot; if timeout, proceed anyway so the\n // queue doesn't stall. Telegram will return 429 if we're still over the\n // limit, and the api-client's retry logic will handle it.\n await bucket.waitForToken(5_000);\n\n const res = await this.#bot.sendMessage(chatId, text);\n if (!res.ok) {\n throw new Error(`Telegram outbound send returned ok=false for chat ${chatId}`);\n }\n return res;\n }\n\n /** Manual send (telegram_send tool, /telegram:send): never silently dropped. */\n async sendManual(\n chatId: string | number,\n text: string,\n ): Promise<TelegramBotResponse<TelegramApiMessage>> {\n if (this.#stopped) {\n throw new Error('Telegram outbound queue is stopped');\n }\n return (await this.#queue.enqueue({\n chatId,\n text,\n kind: 'manual',\n })) as TelegramBotResponse<TelegramApiMessage>;\n }\n\n /**\n * Notification send (session ended, long tool, delegate): fire-and-forget.\n * The returned promise resolves as soon as the queue accepts the entry;\n * downstream send failures are logged and counted but not surfaced.\n */\n enqueueNotification(chatId: string | number, text: string): void {\n if (this.#stopped) {\n this.#log.debug(`Telegram outbound queue ignored notification for chat ${chatId}: stopped`);\n return;\n }\n const entry: OutboundEntry = { chatId, text, kind: 'notification' };\n this.#queue.enqueue(entry).catch((err) => {\n this.#log.debug(\n `Telegram outbound notification enqueue rejected for chat ${chatId}: ${(err as Error).message}`,\n );\n });\n }\n\n stats() {\n return this.#queue.stats();\n }\n\n async stop(): Promise<void> {\n this.#stopped = true;\n await this.#queue.stop();\n }\n}\n", "/**\n * TelegramNotificationChannel \u2014 NotificationChannel implementation for\n * one-way Telegram message delivery.\n *\n * This is the **notification-only** path: fire-and-forget messages sent to\n * a configured chat when the Notifier routes a `NotificationMessage` to\n * the `\"telegram\"` channel. It wraps `bot.sendMessage()` with the standard\n * scrubbing and truncation pipeline used by every outgoing Telegram message.\n *\n * This channel does NOT handle:\n * - 2-way communication (telegram_read, telegram_approve, inline keyboards)\n * - Manual sends via the telegram_send tool (those go through\n * `TelegramBotOutbound.sendManual()` for queue ordering + error surfacing)\n * - Slash commands or polling\n *\n * Those remain in the main `@wrongstack/telegram` plugin, which continues\n * to own the TelegramBot instance, the inbound poller, the outbound queue\n * for manual sends, and the system prompt contributor.\n *\n * @module telegram\n * @public\n */\n\nimport type { Logger } from '@wrongstack/core/types';\nimport type {\n NotificationChannel,\n NotificationLevel,\n NotificationMessage,\n NotificationResult,\n} from '@wrongstack/core/notifications';\nimport type { TelegramBot } from './bot.js';\nimport { scrubTelegramOutboundText } from './security/outbound.js';\nimport { truncateForTelegram } from './bot.js';\n\n// ---------------------------------------------------------------------------\n// Level \u2192 emoji mapping for Telegram preview\n// ---------------------------------------------------------------------------\n\nconst LEVEL_ICON: Record<NotificationLevel, string> = {\n info: '\u2139\uFE0F',\n warning: '\u26A0\uFE0F',\n critical: '\uD83D\uDEA8',\n};\n\n// ---------------------------------------------------------------------------\n// Channel\n// ---------------------------------------------------------------------------\n\nexport interface TelegramNotificationChannelOptions {\n /** The TelegramBot instance (owned by the main plugin). */\n readonly bot: TelegramBot;\n /** Queue-backed notification sender used by the plugin runtime. */\n readonly enqueueNotification?:\n | ((chatId: string | number, text: string) => void)\n | undefined;\n /** Target chat or user ID for all notifications sent through this channel. */\n readonly chatId: string | number;\n /**\n * Maximum message length in characters. Default 4000 (Telegram's hard\n * cap is 4096; `truncateForTelegram` clamps internally). */\n readonly maxMessageLength?: number | undefined;\n /** Logger for debug-level diagnostics. */\n readonly log?: Logger | undefined;\n}\n\nexport class TelegramNotificationChannel implements NotificationChannel {\n readonly name = 'telegram' as const;\n readonly type = 'telegram' as const;\n\n readonly #bot: TelegramBot;\n readonly #chatId: string | number;\n readonly #enqueueNotification:\n | ((chatId: string | number, text: string) => void)\n | undefined;\n readonly #maxLen: number;\n readonly #log: Logger | undefined;\n\n constructor(opts: TelegramNotificationChannelOptions) {\n this.#bot = opts.bot;\n this.#chatId = opts.chatId;\n this.#enqueueNotification = opts.enqueueNotification;\n this.#maxLen = opts.maxMessageLength ?? 4000;\n this.#log = opts.log;\n }\n\n /**\n * Deliver a notification message to the configured Telegram chat.\n *\n * Renders the `NotificationMessage` into a single Telegram text message:\n * - Prepends a level-based emoji icon (\u2139\uFE0F / \u26A0\uFE0F / \uD83D\uDEA8)\n * - Combines `title` (when present) and `body`\n * - Runs through credential scrubbing\n * - Truncates to the configured max length\n *\n * **Does not throw.** Transport errors are caught and returned as\n * `{ ok: false, error: \"\u2026\" }`.\n */\n async deliver(msg: NotificationMessage): Promise<NotificationResult> {\n const deliveredAt = new Date().toISOString();\n try {\n // 1. Render the message for Telegram with level-based icon\n const icon = LEVEL_ICON[msg.level] ?? LEVEL_ICON.info;\n const parts: string[] = [];\n if (msg.title) parts.push(msg.title);\n parts.push(msg.body);\n const rawText = `${icon} ${parts.join('\\n')}`;\n\n // 2. Scrub credentials \u2014 runs BEFORE truncation so a credential\n // is never split across the boundary where the pattern can't match.\n const scrubbed = scrubTelegramOutboundText(rawText);\n\n // 3. Truncate to fit Telegram's message size limit\n const truncated = truncateForTelegram(scrubbed, this.#maxLen);\n\n // 4. Queue when the plugin runtime supplies its bounded outbound path.\n if (this.#enqueueNotification) {\n this.#enqueueNotification(this.#chatId, truncated);\n this.#log?.debug?.(`telegram notification queued (${truncated.length} chars)`);\n return { ok: true, channel: this.name, deliveredAt };\n }\n\n // Standalone channels retain direct delivery for backwards compatibility.\n const res = await this.#bot.sendMessage(this.#chatId, truncated);\n\n this.#log?.debug?.(`telegram notification delivered (${truncated.length} chars, ok=${res.ok})`);\n\n return {\n ok: res.ok,\n channel: this.name,\n ...(res.ok ? {} : { error: `Telegram API returned ok=false` }),\n deliveredAt,\n };\n } catch (err) {\n this.#log?.debug?.(\n `telegram notification delivery failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return {\n ok: false,\n channel: this.name,\n error: err instanceof Error ? err.message : String(err),\n deliveredAt,\n };\n }\n }\n\n /**\n * Liveness probe \u2014 delegates to the bot's health check.\n * Returns `{ ok: true }` when the bot token is valid and\n * api.telegram.org is reachable.\n */\n async ping(): Promise<{ ok: boolean; error?: string | undefined }> {\n try {\n const h = await this.#bot.health();\n return { ok: h.ok, ...(h.ok ? {} : { error: h.error ?? 'health check failed' }) };\n } catch (err) {\n return {\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n", "import type { Tool } from '@wrongstack/core/types';\nimport type { TelegramBot } from '../bot.js';\n\ninterface TelegramReadInput {\n /** Filter to messages from a specific chat/user ID. Omit to see all chats. */\n chat_id?: string | number | undefined;\n /** Max messages to return (default: 10, max: 50). */\n limit?: number | undefined;\n /**\n * If a message_id is provided, acknowledge all messages up to and\n * including this ID (mark them as processed / remove from buffer).\n */\n ack_last?: number | undefined;\n}\n\nexport function makeTelegramReadTool(opts: {\n bot: TelegramBot;\n}): Tool<TelegramReadInput> {\n return {\n name: 'telegram_read',\n description:\n 'Read recent incoming Telegram messages the bot has received, newest first. Returns messages with sender, text, and timestamp. After reading, acknowledge them with ack_last so they are cleared. When responding to a user via telegram_send, format your reply as natural prose \u2014 summarize findings, report outcomes clearly, do not paste raw data.',\n usageHint: 'telegram_read(chat_id: \"123456789\", limit: 5, ack_last: 42) \u2014 read messages, then ack the highest message_id to clear them.',\n category: 'Telegram',\n inputSchema: {\n type: 'object',\n properties: {\n chat_id: {\n oneOf: [{ type: 'string' }, { type: 'integer' }],\n description: 'Read messages only from this chat/user.',\n },\n limit: {\n type: 'integer',\n minimum: 1,\n maximum: 50,\n description: 'Max messages to return (default: 10).',\n },\n ack_last: {\n type: 'integer',\n description:\n 'After processing messages, pass the highest message_id to clear them from the buffer.',\n },\n },\n },\n permission: 'auto',\n mutating: false,\n timeoutMs: 5_000,\n async execute(input) {\n const msgs = opts.bot.getMessages({\n chatId: input.chat_id,\n limit: input.limit ?? 10,\n });\n\n let acked = 0;\n if (input.ack_last !== undefined && input.ack_last > 0) {\n acked = opts.bot.acknowledge(input.ack_last);\n }\n\n return {\n buffer_total: opts.bot.bufferCount,\n messages: msgs.map((m) => ({\n message_id: m.messageId,\n chat_id: m.chatId,\n chat_type: m.chatType,\n from: m.userName ?? `user_${m.userId ?? 'unknown'}`,\n text: m.text,\n ts: new Date(m.timestamp).toISOString(),\n })),\n acked,\n hint: acked > 0\n ? undefined\n : 'Use ack_last with the highest message_id to clear processed messages.',\n };\n },\n };\n}\n", "import { ToolCapabilities } from '@wrongstack/core/security';\nimport type { Logger, Tool } from '@wrongstack/core/types';\nimport type { TelegramBot } from '../bot.js';\nimport {\n resolveTelegramOutboundTarget,\n scrubTelegramOutboundText,\n type TelegramChatId,\n} from '../security/outbound.js';\nimport { truncateForTelegram } from '../bot.js';\n\ninterface TelegramSendInput {\n /** Chat or user ID to send the message to. Falls back to config.notifyChatId when omitted. */\n chat_id?: string | number | undefined;\n /** Message text. */\n message: string;\n}\n\nexport function makeTelegramSendTool(opts: {\n bot: TelegramBot;\n /** Paired/default target, resolved on every call for live config updates. */\n getDefaultChatId(): TelegramChatId | undefined;\n /** Additional trusted targets, resolved on every call for live config updates. */\n getAllowedOutboundChatIds?(): readonly TelegramChatId[];\n maxMessageLength: number;\n log: Logger;\n}): Tool<TelegramSendInput> {\n return {\n name: 'telegram_send',\n description:\n 'Send a scrubbed message to the paired Telegram chat or an explicitly allowed outbound chat. Write natural prose for a human reader; summarize results and never paste raw JSON, object dumps, credentials, or truncated tool output.',\n usageHint:\n 'telegram_send(chat_id: \"123456789\", message: \"Build completed \u2014 12 tests passed, 0 failed. Deploying to staging now.\")',\n category: 'Telegram',\n inputSchema: {\n type: 'object',\n properties: {\n chat_id: {\n oneOf: [{ type: 'string' }, { type: 'integer' }],\n description: 'Target chat or user ID. Uses the plugin default when omitted.',\n },\n message: {\n type: 'string',\n description:\n 'Message text in natural, human-readable prose. Summarize results, include only key details. Do NOT paste raw JSON, object dumps, or unformatted tool output. Target 1\u20134 lines for readability on mobile.',\n },\n },\n required: ['message'],\n },\n permission: 'confirm',\n mutating: true,\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n timeoutMs: 15_000,\n async execute(input, _ctx, toolOpts) {\n const chatId = resolveTelegramOutboundTarget(input.chat_id, opts);\n\n // Scrub before truncation so a credential is never split into fragments\n // that no longer match the shared detector.\n const scrubbed = scrubTelegramOutboundText(input.message);\n const truncated = truncateForTelegram(scrubbed, opts.maxMessageLength);\n\n opts.log.info(`telegram_send \u2192 chat_id=${chatId} (${truncated.length} chars)`);\n\n const res = toolOpts?.signal\n ? await opts.bot.sendMessage(chatId, truncated, toolOpts.signal)\n : await opts.bot.sendMessage(chatId, truncated);\n\n return {\n ok: res.ok,\n message_id: res.result?.message_id,\n chat: res.result?.chat\n ? {\n id: res.result.chat.id,\n type: res.result.chat.type,\n title: res.result.chat.title,\n }\n : undefined,\n };\n },\n };\n}\n", "// ---------------------------------------------------------------------------\n// P2.2 \u2014 Classify each Telegram runtime config key as either\n// \"hot-reload-safe\" or \"requires restart\".\n//\n// The plugin can apply changes live for keys marked HOT. Anything else\n// (identity, transport, capacity) touches the long-lived bot, the\n// outbound-queue worker, or the poll lock \u2014 so it must surface a restart\n// hint to the operator rather than silently misbehave.\n//\n// This module is a compatibility facade over the Core lifecycle metadata:\n// no fs, no network, no side effects. TELEGRAM_CONFIG_FIELDS is the single\n// source of truth and is exhaustive at the type level. Unknown keys are\n// defaulted to 'immutable' inside diffPluginConfig (Core); anything not\n// 'hot' (i.e. both 'restart' and 'immutable') is folded to 'restart-required'\n// here, so a newly added or restart-required field stays restart-required\n// until explicitly marked HOT.\n// ---------------------------------------------------------------------------\n\nimport { diffPluginConfig } from '@wrongstack/core/plugin';\nimport { TELEGRAM_CONFIG_FIELDS, type TelegramPluginConfig } from './config.js';\n\nexport type ConfigReloadClass = 'hot' | 'restart-required';\n\n/**\n * Diff two configs and return the keys whose values differ, each tagged\n * with its reload classification. Keys present in `previous` but absent in\n * `next` (or vice versa) are reported as `'restart-required'` so a stale\n * removal is never silently applied as a live change.\n */\nexport function diffConfigKeys(\n previous: TelegramPluginConfig,\n next: TelegramPluginConfig,\n): Array<{ key: keyof TelegramPluginConfig; classification: ConfigReloadClass }> {\n // Double cast: TelegramPluginConfig has optional fields, so its structural\n // type is not assignable to Record<string, unknown> directly. The key cast\n // below is safe because diffPluginConfig only iterates Object.keys(prev) \u222A Object.keys(next).\n return diffPluginConfig(\n previous as unknown as Record<string, unknown>,\n next as unknown as Record<string, unknown>,\n TELEGRAM_CONFIG_FIELDS,\n ).map((change) => ({\n key: change.key as keyof TelegramPluginConfig,\n classification: change.lifecycle === 'hot' ? 'hot' : 'restart-required',\n }));\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { PluginAPI } from '@wrongstack/core/plugin';\nimport type { Config, Logger, Plugin, SlashCommand } from '@wrongstack/core/types';\nimport { expectDefined } from '@wrongstack/core/utils';\nimport type { TelegramIncomingMessage } from './bot.js';\nimport { TelegramBot } from './bot.js';\nimport {\n DEFAULT_CONFIG,\n PLUGIN_CONFIG_ALIASES,\n PLUGIN_NAME,\n readTelegramConfig,\n readTelegramConfigFromConfig,\n TELEGRAM_CONFIG_FIELDS,\n telegramConfigSchema,\n} from './config.js';\nimport type { SessionEndedLike, ToolExecutedLike } from './format.js';\nimport { formatDelegateCompleted, formatSessionEnded, formatToolExecuted } from './format.js';\nimport { lockPathForToken, PollLock } from './poll-lock.js';\nimport { OffsetStore } from './offset-store.js';\nimport { scrubTelegramOutboundText } from './security/outbound.js';\nimport { tgChatIdCommand, tgHealthCommand, tgSendCommand } from './slash-commands/index.js';\nimport { makeTelegramApproveTool } from './tools/telegram-approve.js';\nimport { TelegramBotOutbound } from './bot-queue.js';\nimport { TelegramNotificationChannel } from './notification-channel.js';\nimport { makeTelegramReadTool } from './tools/telegram-read.js';\nimport { makeTelegramSendTool } from './tools/telegram-send.js';\nimport { diffConfigKeys } from './config-classifier.js';\n\n// ---------------------------------------------------------------------------\n// Teardown state\n// ---------------------------------------------------------------------------\n\n/** Mutable runtime config \u2014 updated via api.onConfigChange so changes take\n * effect without restarting the plugin. */\ninterface RuntimeConfig {\n notifyChatId: string | number | undefined;\n allowedOutboundChats: Array<string | number>;\n allowedUserIds: Array<string | number>;\n allowGroupApprovals: boolean;\n notifyOnSessionEnd: boolean;\n notifyOnDelegate: boolean;\n longToolThresholdMs: number;\n maxMessageLength: number;\n outboundQueuePerChat: number;\n outboundQueueConcurrency: number;\n /** Per-chat rate-limit pacing for outbound sends (live via getter). */\n rateLimitTokensPerSecond: number;\n /** Per-chat rate-limit burst size for outbound sends (live via getter). */\n rateLimitBurst: number;\n /** Telegram parse mode resolved per-send so hot-reload takes effect live. */\n parseMode: '' | 'HTML' | 'MarkdownV2';\n}\n\ninterface RuntimeState {\n bot: TelegramBot;\n outbound: TelegramBotOutbound;\n cleanups: Array<() => void>;\n}\n\nlet teardownState: RuntimeState | null = null;\n\nconst DENY_ALL_INBOUND = '__wrongstack_telegram_inbound_disabled__';\n\nfunction inboundAllowlist(cfg: ReturnType<typeof readTelegramConfig>): {\n allowedUsers: Set<string>;\n allowedChats: Set<string>;\n} {\n if (cfg.inboundMode === 'public') {\n return { allowedUsers: new Set(), allowedChats: new Set() };\n }\n if (cfg.inboundMode === 'paired') {\n const pairedUsers = new Set((cfg.allowedUsers ?? []).map(String));\n return {\n allowedUsers:\n pairedUsers.size > 0 ? pairedUsers : new Set([String(expectDefined(cfg.notifyChatId))]),\n allowedChats: new Set([String(expectDefined(cfg.notifyChatId))]),\n };\n }\n if (cfg.inboundMode === 'allowlist') {\n return {\n allowedUsers: new Set((cfg.allowedUsers ?? []).map(String)),\n allowedChats: new Set((cfg.allowedChats ?? []).map(String)),\n };\n }\n return {\n allowedUsers: new Set([DENY_ALL_INBOUND]),\n allowedChats: new Set([DENY_ALL_INBOUND]),\n };\n}\n\nfunction runCleanups(cleanups: Array<() => void>, log: Logger): void {\n while (cleanups.length > 0) {\n const cleanup = cleanups.pop();\n try {\n cleanup?.();\n } catch (err) {\n log.debug(`Telegram cleanup failed: ${(err as Error).message}`);\n }\n }\n}\n\nfunction disposeRuntime(log: Logger): void {\n const state = teardownState;\n teardownState = null;\n if (state) runCleanups(state.cleanups, log);\n}\n\nfunction registerCommand(api: PluginAPI, command: SlashCommand, cleanups: Array<() => void>): void {\n api.slashCommands.register(command);\n cleanups.push(() => {\n api.slashCommands.unregister(`${PLUGIN_NAME}:${command.name}`);\n });\n}\n\n/**\n * Build the plugin's `defaultConfig` from `DEFAULT_CONFIG` with defensive\n * deep copies of every field (arrays and any future nested values).\n * Using `structuredClone` ensures any key added to `DEFAULT_CONFIG` is\n * automatically copied rather than shared by reference, preventing silent\n * drift from the documented defaults. Scalar (immutable) values are\n * unaffected by deep copy.\n *\n * Return type widens to `Record<string, unknown>` to align with the\n * `Plugin.defaultConfig` interface (see `packages/core/src/types/plugin.ts`).\n * The narrower `DEFAULT_CONFIG` shape is still enforced by\n * `telegramConfigSchema` validation downstream.\n */\nfunction telegramDefaultConfig(): Record<string, unknown> {\n return structuredClone(DEFAULT_CONFIG) as Record<string, unknown>;\n}\n\n/** Read the Telegram section from a full Config object.\n * Delegates to {@link readTelegramConfigFromConfig} for the canonical\n * extension-slice extraction + default merge, then applies the runtime\n * coercions that `RuntimeConfig` requires (String chatId, array copies). */\nfunction telegramFromConfig(cfg: Config): {\n notifyChatId: string | number | undefined;\n allowedOutboundChats: Array<string | number>;\n allowedUserIds: Array<string | number>;\n allowGroupApprovals: boolean;\n notifyOnSessionEnd: boolean;\n notifyOnDelegate: boolean;\n longToolThresholdMs: number;\n maxMessageLength: number;\n outboundQueuePerChat: number;\n outboundQueueConcurrency: number;\n rateLimitTokensPerSecond: number;\n rateLimitBurst: number;\n parseMode: '' | 'HTML' | 'MarkdownV2';\n} {\n const tg = readTelegramConfigFromConfig(cfg);\n return {\n notifyChatId: tg.notifyChatId !== undefined ? String(tg.notifyChatId) : undefined,\n allowedOutboundChats: [...(tg.allowedOutboundChats ?? [])],\n allowedUserIds: [...(tg.allowedUsers ?? [])],\n allowGroupApprovals: tg.allowGroupApprovals ?? false,\n notifyOnSessionEnd: tg.notifyOnSessionEnd ?? false,\n notifyOnDelegate: tg.notifyOnDelegate ?? true,\n longToolThresholdMs: tg.longToolThresholdMs ?? 30_000,\n maxMessageLength: tg.maxMessageLength ?? 4000,\n outboundQueuePerChat: tg.outboundQueuePerChat ?? 32,\n outboundQueueConcurrency: tg.outboundQueueConcurrency ?? 4,\n rateLimitTokensPerSecond: tg.rateLimitTokensPerSecond ?? 0.33,\n rateLimitBurst: tg.rateLimitBurst ?? 1,\n parseMode: tg.parseMode ?? '',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Plugin\n// ---------------------------------------------------------------------------\n\nconst plugin: Plugin = {\n name: PLUGIN_NAME,\n version: '0.3.4',\n description: 'Telegram bridge \u2014 send/receive messages, get agent notifications.',\n apiVersion: '^0.1.10',\n capabilities: {\n tools: true,\n slashCommands: true,\n pipelines: [],\n },\n configAliases: [...PLUGIN_CONFIG_ALIASES],\n configFields: TELEGRAM_CONFIG_FIELDS,\n configSchema: telegramConfigSchema,\n defaultConfig: telegramDefaultConfig(),\n\n async setup(api) {\n const log = api.log;\n disposeRuntime(log);\n const cfg = readTelegramConfig(api);\n\n log.info('Starting Telegram plugin...');\n\n // ---- Mutable runtime config (updated via onConfigChange) ----\n const runtimeCfg: RuntimeConfig = {\n notifyChatId: cfg.notifyChatId,\n allowedOutboundChats: [...(cfg.allowedOutboundChats ?? [])],\n allowedUserIds: [...(cfg.allowedUsers ?? [])],\n allowGroupApprovals: cfg.allowGroupApprovals ?? false,\n notifyOnSessionEnd: cfg.notifyOnSessionEnd ?? false,\n notifyOnDelegate: cfg.notifyOnDelegate ?? true,\n longToolThresholdMs: cfg.longToolThresholdMs ?? 30_000,\n maxMessageLength: cfg.maxMessageLength ?? 4000,\n outboundQueuePerChat: cfg.outboundQueuePerChat ?? 32,\n outboundQueueConcurrency: cfg.outboundQueueConcurrency ?? 4,\n rateLimitTokensPerSecond: cfg.rateLimitTokensPerSecond ?? 0.33,\n rateLimitBurst: cfg.rateLimitBurst ?? 1,\n parseMode: cfg.parseMode ?? '',\n };\n\n // ---- Bot ----\n // Telegram allows one getUpdates consumer per token: elect a single\n // poller across wstack instances so concurrent TUI/WebUI/projects don't\n // fight over the token (HTTP 409 on every poll).\n const lock =\n cfg.singleInstanceLock === false\n ? undefined\n : new PollLock(lockPathForToken(cfg.botToken), { log });\n // Persist the polling cursor so a crash/restart doesn't replay messages.\n // Default to a token-scoped store under ~/.wrongstack/telegram; an explicit\n // offsetStoragePath overrides the location. Persistence is disabled only\n // when offsetStoragePath is set to an empty string.\n const offsetStore =\n cfg.offsetStoragePath === ''\n ? undefined\n : new OffsetStore({ token: cfg.botToken, path: cfg.offsetStoragePath });\n const bot = new TelegramBot({\n token: cfg.botToken,\n pollIntervalSec: cfg.pollIntervalSec ?? 2,\n ...inboundAllowlist(cfg),\n bufferSize: 50,\n log,\n offsetStore,\n lock,\n getParseMode: () => runtimeCfg.parseMode,\n onMessage(msg: TelegramIncomingMessage) {\n // Emit custom event so other plugins or the host can react.\n // The TUI can subscribe and surface it (future hook).\n api.emitCustom('telegram:message_received', msg);\n\n // Bridge to the inter-agent mailbox so other agents (leader,\n // background watchers) can discover Telegram messages without\n // polling the bot buffer. Broadcast as a low-priority note so\n // it informs without interrupting. Mailbox delivery is\n // best-effort \u2014 failure must never disrupt the bot's polling\n // loop, but surface it for debugging.\n const mailbox = api.mailbox;\n if (mailbox) {\n mailbox\n .send({\n from: 'telegram',\n to: 'leader',\n type: 'note',\n subject: scrubTelegramOutboundText(\n `\uD83D\uDCE8 Telegram from ${msg.userName ?? `user_${msg.userId ?? 'unknown'}`}`,\n ),\n body: scrubTelegramOutboundText(msg.text),\n priority: 'low',\n })\n .catch((err: unknown) => {\n log.debug(`Telegram\u2192mailbox bridge delivery failed: ${(err as Error).message}`);\n });\n }\n\n // Keep untrusted inbound content in the bot buffer only. Logs expose\n // bounded metadata so message text, sender, and chat IDs cannot leak.\n log.info(`\uD83D\uDCE8 Telegram message received (${Math.min(bot.bufferCount, 50)} unread)`);\n },\n });\n\n // Validate the token before mutating host registries or acquiring the poll\n // lock. A failed preflight must leave setup observationally atomic.\n const probe = await bot.health();\n if (!probe.ok) {\n bot.stop();\n throw new Error(\n `Telegram plugin startup failed: ${probe.error ?? 'unknown error'}. ` +\n `Verify botToken in extensions.telegram (token from @BotFather, format \"<id>:<35+ chars>\").`,\n );\n }\n log.info(`Telegram self-test ok: @${probe.username ?? 'unknown'} (api.telegram.org reachable)`);\n\n const cleanups: Array<() => void> = [];\n try {\n // Bot cleanup is registered first so it runs last, after every host-side\n // listener and registry entry has been detached.\n cleanups.push(() => bot.stop());\n\n // Bounded outbound queue with per-chat backpressure. Notification events\n // and the /telegram:send slash command both enqueue through it; manual\n // telegram_send tool sends go through bot.sendMessage directly so user\n // errors surface immediately. The queue is drained on teardown.\n // Rate-limit fields are passed as getter callbacks so hot-reload\n // (via api.onConfigChange) takes effect on the next send without\n // rebuilding the queue.\n const outbound = new TelegramBotOutbound({\n bot,\n log,\n maxPerChat: runtimeCfg.outboundQueuePerChat,\n maxConcurrency: runtimeCfg.outboundQueueConcurrency,\n getRateLimitTokensPerSecond: () => runtimeCfg.rateLimitTokensPerSecond,\n getRateLimitBurst: () => runtimeCfg.rateLimitBurst,\n });\n cleanups.push(() => {\n void outbound.stop();\n });\n\n // ---- Register tools ----\n const sendTool = makeTelegramSendTool({\n bot,\n getDefaultChatId: () => runtimeCfg.notifyChatId,\n getAllowedOutboundChatIds: () => runtimeCfg.allowedOutboundChats,\n maxMessageLength: runtimeCfg.maxMessageLength,\n log,\n });\n const readTool = makeTelegramReadTool({ bot });\n const approveTool = makeTelegramApproveTool({\n bot,\n getDefaultChatId: () => runtimeCfg.notifyChatId,\n getAllowedOutboundChatIds: () => runtimeCfg.allowedOutboundChats,\n getAllowedUserIds: () => runtimeCfg.allowedUserIds,\n getAllowGroupApprovals: () => runtimeCfg.allowGroupApprovals,\n maxMessageLength: runtimeCfg.maxMessageLength,\n log,\n });\n for (const tool of [sendTool, readTool, approveTool]) {\n api.tools.register(tool);\n cleanups.push(() => {\n api.tools.unregister(tool.name);\n });\n }\n\n // ---- Event subscriptions ----\n\n // System prompts receive metadata only. Message text and identity stay\n // behind the explicit telegram_read tool boundary.\n const unregisterPrompt = api.registerSystemPromptContributor(async () => {\n const unreadCount = Math.min(bot.bufferCount, 50);\n if (unreadCount === 0) return [];\n return [\n {\n type: 'text' as const,\n text: [\n '## Telegram Inbox',\n `You have ${unreadCount} unread Telegram message(s).`,\n 'Use `telegram_read` to retrieve them when needed.',\n ].join('\\n'),\n },\n ];\n });\n cleanups.push(unregisterPrompt);\n\n // Register commands one at a time so a later collision can roll back the\n // commands already installed by this setup attempt.\n for (const command of [\n tgHealthCommand(bot, cfg),\n tgSendCommand(\n bot,\n {\n getDefaultChatId: () => runtimeCfg.notifyChatId,\n getAllowedOutboundChatIds: () => runtimeCfg.allowedOutboundChats,\n getMaxMessageLength: () => runtimeCfg.maxMessageLength,\n },\n outbound,\n ),\n tgChatIdCommand(cfg.notifyChatId),\n ]) {\n registerCommand(api, command, cleanups);\n }\n\n // ---- Notification channel ----\n // The `TelegramNotificationChannel` is registered with the host's\n // `Notifier` (api.notifier) when available, so the central router can\n // route `\"telegram\"`-channel notifications directly. The in-plugin\n // event handlers below still handle the \"when to notify\" logic; they\n // send through the channel directly. Once the central Notifier router\n // is fully built, these handlers move to the router entirely.\n let notifyChannel: TelegramNotificationChannel | undefined;\n if (runtimeCfg.notifyChatId !== undefined) {\n notifyChannel = new TelegramNotificationChannel({\n bot,\n chatId: runtimeCfg.notifyChatId,\n maxMessageLength: runtimeCfg.maxMessageLength,\n enqueueNotification: (chatId, text) => outbound.enqueueNotification(chatId, text),\n log,\n });\n // Register with the host's Notifier so other subsystems can send\n // notifications to the \"telegram\" channel without knowing Telegram.\n api.notifier?.registerChannel(notifyChannel);\n }\n\n // ---- Notification event handlers ----\n // Always subscribed; guard at event time against runtime flags so changes\n // take effect immediately without needing to restart the plugin.\n // Delivery goes through `notifyChannel` for rendering/scrubbing and then\n // through the shared outbound queue for ordering and backpressure.\n\n cleanups.push(\n api.events.on('session.ended', (event) => {\n if (!runtimeCfg.notifyOnSessionEnd || !runtimeCfg.notifyChatId || !notifyChannel) return;\n const payload: SessionEndedLike = {\n id: scrubTelegramOutboundText(event.id),\n inputTokens: event.usage.input,\n outputTokens: event.usage.output,\n cacheRead: event.usage.cacheRead,\n cacheWrite: event.usage.cacheWrite,\n };\n notifyChannel.deliver({\n title: 'Session ended',\n body: formatSessionEnded(payload),\n level: 'info',\n source: 'session.end',\n }).then(r => {\n if (!r.ok) log.warn(`session.ended notification delivery failed: ${r.error ?? 'unknown'}`);\n }).catch((err: unknown) => {\n log.debug(`session.ended notification delivery threw: ${(err as Error).message}`);\n });\n }),\n );\n\n cleanups.push(\n api.events.on('tool.executed', (event) => {\n if (!runtimeCfg.notifyChatId || !notifyChannel || runtimeCfg.longToolThresholdMs <= 0) return;\n if (event.durationMs < runtimeCfg.longToolThresholdMs) return;\n const payload: ToolExecutedLike = {\n name: event.name,\n ok: event.ok,\n durationMs: event.durationMs,\n output:\n event.output === undefined ? undefined : scrubTelegramOutboundText(event.output),\n };\n notifyChannel.deliver({\n title: event.ok ? 'Tool completed' : 'Tool failed',\n body: formatToolExecuted(payload),\n level: event.ok ? 'info' : 'warning',\n source: 'tool.exec',\n }).then(r => {\n if (!r.ok) log.warn(`tool.executed notification delivery failed: ${r.error ?? 'unknown'}`);\n }).catch((err: unknown) => {\n log.debug(`tool.executed notification delivery threw: ${(err as Error).message}`);\n });\n }),\n );\n\n cleanups.push(\n api.events.on('delegate.completed', (event) => {\n if (!runtimeCfg.notifyOnDelegate || !runtimeCfg.notifyChatId || !notifyChannel) return;\n const safeEvent = {\n ...event,\n target: scrubTelegramOutboundText(event.target),\n task: scrubTelegramOutboundText(event.task),\n status:\n event.status === undefined ? undefined : scrubTelegramOutboundText(event.status),\n summary: scrubTelegramOutboundText(event.summary),\n };\n notifyChannel.deliver({\n title: `Delegate: ${safeEvent.target}`,\n body: formatDelegateCompleted(safeEvent),\n level: event.ok ? 'info' : 'warning',\n source: 'delegate.completed',\n }).then(r => {\n if (!r.ok) log.warn(`delegate.completed notification delivery failed: ${r.error ?? 'unknown'}`);\n }).catch((err: unknown) => {\n log.debug(`delegate.completed notification delivery threw: ${(err as Error).message}`);\n });\n }),\n );\n\n // ---- Live config updates (P2.3 \u2014 atomic reconfiguration) ----\n // api.config is frozen at setup, but onConfigChange fires whenever the\n // ConfigStore is updated (from CLI /settings, WebUI prefSync, /telegram-settings).\n //\n // P2.2 classifies each changed key as HOT (apply live) or RESTART\n // (requires bot rebuild). HOT keys mutate the shared runtimeCfg so\n // every handler picks up the new value on the next event. RESTART\n // keys are logged with a restart hint \u2014 the operator must restart the\n // plugin for those to take effect. A future iteration will attempt\n // an atomic bot rebuild for restart keys (build \u2192 health-check \u2192\n // swap \u2192 rollback on failure).\n const unlistenConfig = api.onConfigChange((next, prev) => {\n // Build full TelegramPluginConfig snapshots for the P2.2 classifier.\n const nextTg = readTelegramConfigFromConfig(next);\n const prevTg = readTelegramConfigFromConfig(prev);\n const changedKeys = diffConfigKeys(prevTg, nextTg);\n const hotKeys = changedKeys.filter(c => c.classification === 'hot').map(c => c.key);\n const restartKeys = changedKeys.filter(c => c.classification === 'restart-required').map(c => c.key);\n\n // Phase 1: Apply hot keys to the shared runtime config.\n // Only keys classified as hot-reload-safe are applied live.\n // Restart-required keys (notifyChatId, outboundQueue*, singleInstanceLock,\n // botToken, offsetStoragePath) keep their previous values until\n // the operator restarts the plugin.\n const fresh = telegramFromConfig(next);\n const was = telegramFromConfig(prev);\n const hotSet = new Set<string>(hotKeys);\n\n // Table-driven hot-apply. Each entry maps a Telegram config key\n // to the runtime mutation to perform when that key changed.\n // Adding a new hot key here is a one-line change instead of a\n // duplicated `if (hotSet.has(...))` arm.\n type HotApplier = (runtimeCfg: RuntimeConfig, fresh: ReturnType<typeof telegramFromConfig>) => void;\n const HOT_APPLIERS: ReadonlyMap<string, HotApplier> = new Map<string, HotApplier>([\n ['allowedOutboundChats', (r, f) => { r.allowedOutboundChats = f.allowedOutboundChats; }],\n ['allowedUsers', (r, f) => { r.allowedUserIds = f.allowedUserIds; }],\n ['notifyOnSessionEnd', (r, f) => { r.notifyOnSessionEnd = f.notifyOnSessionEnd; }],\n ['notifyOnDelegate', (r, f) => { r.notifyOnDelegate = f.notifyOnDelegate; }],\n ['longToolThresholdMs', (r, f) => { r.longToolThresholdMs = f.longToolThresholdMs; }],\n ['maxMessageLength', (r, f) => { r.maxMessageLength = f.maxMessageLength; }],\n ['allowGroupApprovals', (r, f) => { r.allowGroupApprovals = f.allowGroupApprovals; }],\n ['rateLimitTokensPerSecond', (r, f) => { r.rateLimitTokensPerSecond = f.rateLimitTokensPerSecond; }],\n ['rateLimitBurst', (r, f) => { r.rateLimitBurst = f.rateLimitBurst; }],\n ['parseMode', (r, f) => { r.parseMode = f.parseMode; }],\n ]);\n for (const [key, apply] of HOT_APPLIERS) {\n if (hotSet.has(key)) apply(runtimeCfg, fresh);\n }\n // notifyChatId is RESTART-REQUIRED: the inbound allowlist\n // (built at setup) still uses the old value. Applying the new\n // one here would make outbound notifications target a chat the\n // inbound allowlist does not yet recognise. Deferred to restart.\n\n // When maxMessageLength changes (hot), rebuild the notification\n // channel so it picks up the new limit. notifyChatId changes\n // are deferred to restart, so the channel keeps the current\n // (old) chat ID.\n if (hotSet.has('maxMessageLength') && fresh.maxMessageLength !== was.maxMessageLength) {\n notifyChannel =\n runtimeCfg.notifyChatId !== undefined\n ? new TelegramNotificationChannel({\n bot,\n chatId: runtimeCfg.notifyChatId,\n maxMessageLength: fresh.maxMessageLength,\n enqueueNotification: (chatId, text) =>\n outbound.enqueueNotification(chatId, text),\n log,\n })\n : undefined;\n }\n\n // Phase 2: Surface restart-required keys.\n if (restartKeys.length > 0) {\n log.warn(\n 'Telegram config changed restart-required keys \u2014 restart the plugin for these to take effect',\n { restartKeys, hotKeys },\n );\n api.emitCustom('telegram:restart_required', {\n keys: restartKeys,\n message: `Restart required for: ${restartKeys.join(', ')}`,\n });\n }\n\n log.debug('Telegram config updated', {\n hotApplied: hotKeys,\n restartRequired: restartKeys,\n notifyOnSessionEnd: runtimeCfg.notifyOnSessionEnd,\n notifyOnDelegate: runtimeCfg.notifyOnDelegate,\n longToolThresholdMs: runtimeCfg.longToolThresholdMs,\n parseMode: runtimeCfg.parseMode,\n notifyChatId: runtimeCfg.notifyChatId ?? 'not set',\n });\n });\n cleanups.push(unlistenConfig);\n\n // Polling is the final side effect: it may acquire the cross-process\n // lock and create timers, and bot.stop() releases all of them.\n bot.start();\n teardownState = { bot, outbound, cleanups };\n log.info('Telegram plugin ready');\n } catch (err) {\n teardownState = null;\n runCleanups(cleanups, log);\n throw err;\n }\n },\n\n async teardown(api) {\n const hadRuntime = teardownState !== null;\n disposeRuntime(api.log);\n if (hadRuntime) api.log.info('Telegram plugin torn down');\n },\n\n async health() {\n const state = teardownState;\n if (!state?.bot) return { ok: false, message: 'Plugin not initialized' };\n const h = await state.bot.health();\n return h;\n },\n};\n\nexport default plugin;\n\n// Exposed for tests to inspect the queue without going through the API surface.\nexport { teardownState };\n\n// Re-export the types and classes consumers may want\nexport type { TelegramIncomingMessage } from './bot.js';\nexport type { TelegramPluginConfig } from './config.js';\nexport { TelegramNotificationChannel } from './notification-channel.js';\nexport type { TelegramNotificationChannelOptions } from './notification-channel.js';\n", "// ---------------------------------------------------------------------------\n// Telegram Bot API models used by the plugin\n// ---------------------------------------------------------------------------\n\nexport interface TelegramApiUser {\n id: number;\n is_bot: boolean;\n first_name: string;\n username?: string | undefined;\n}\n\nexport type TelegramApiChatType = 'private' | 'group' | 'supergroup' | 'channel';\n\nexport interface TelegramApiChat {\n id: number;\n type: TelegramApiChatType;\n title?: string | undefined;\n username?: string | undefined;\n}\n\nexport interface TelegramApiMessage {\n message_id: number;\n from?: TelegramApiUser | undefined;\n chat: TelegramApiChat;\n date: number;\n text?: string | undefined;\n}\n\nexport interface TelegramApiCallbackQuery {\n id: string;\n from?: TelegramApiUser | undefined;\n message?: { message_id: number; chat: TelegramApiChat } | undefined;\n data?: string | undefined;\n}\n\nexport interface TelegramApiUpdate {\n update_id: number;\n message?: TelegramApiMessage | undefined;\n edited_message?: TelegramApiMessage | undefined;\n callback_query?: TelegramApiCallbackQuery | undefined;\n}\n\nexport interface TelegramInlineKeyboardButton {\n text: string;\n callback_data: string;\n}\n\ninterface TelegramApiEnvelope<T> {\n ok: boolean;\n result?: T | undefined;\n description?: string | undefined;\n error_code?: number | undefined;\n parameters?:\n | {\n retry_after?: number | undefined;\n migrate_to_chat_id?: number | undefined;\n }\n | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Structured, token-safe failure model\n// ---------------------------------------------------------------------------\n\nexport type TelegramApiClientErrorKind = 'network' | 'http' | 'parse' | 'api';\n\nexport abstract class TelegramApiClientError extends Error {\n readonly kind: TelegramApiClientErrorKind;\n readonly method: string;\n\n protected constructor(kind: TelegramApiClientErrorKind, method: string, message: string) {\n super(message);\n this.kind = kind;\n this.method = method;\n }\n}\n\nexport class TelegramNetworkError extends TelegramApiClientError {\n readonly detail: string;\n readonly aborted: boolean;\n\n constructor(method: string, detail: string, aborted = false) {\n super('network', method, `Telegram network error during ${method}: ${detail}`);\n this.name = 'TelegramNetworkError';\n this.detail = detail;\n this.aborted = aborted;\n }\n}\n\nexport class TelegramHttpError extends TelegramApiClientError {\n readonly status: number;\n\n constructor(method: string, status: number, statusText?: string | undefined) {\n const suffix = statusText ? ` ${statusText}` : '';\n super('http', method, `Telegram HTTP error during ${method}: ${status}${suffix}`);\n this.name = 'TelegramHttpError';\n this.status = status;\n }\n}\n\nexport class TelegramResponseParseError extends TelegramApiClientError {\n constructor(method: string, detail: string) {\n super('parse', method, `Telegram response parse error during ${method}: ${detail}`);\n this.name = 'TelegramResponseParseError';\n }\n}\n\nexport class TelegramBotApiError extends TelegramApiClientError {\n readonly errorCode?: number | undefined;\n readonly httpStatus?: number | undefined;\n readonly description: string;\n readonly retryAfterSeconds?: number | undefined;\n readonly migrateToChatId?: number | undefined;\n\n constructor(\n method: string,\n opts: {\n errorCode?: number | undefined;\n httpStatus?: number | undefined;\n description: string;\n retryAfterSeconds?: number | undefined;\n migrateToChatId?: number | undefined;\n },\n ) {\n const code = opts.errorCode === undefined ? 'unknown' : String(opts.errorCode);\n super('api', method, `Telegram API error ${code} during ${method}: ${opts.description}`);\n this.name = 'TelegramBotApiError';\n this.errorCode = opts.errorCode;\n this.httpStatus = opts.httpStatus;\n this.description = opts.description;\n this.retryAfterSeconds = opts.retryAfterSeconds;\n this.migrateToChatId = opts.migrateToChatId;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Retry and backoff policy\n// ---------------------------------------------------------------------------\n\nexport interface RetryDecision {\n /** Whether to retry the request. */\n retry: boolean;\n /** Milliseconds to wait before retrying. 0 when retry is false. */\n delayMs: number;\n}\n\n/** Base delay for exponential backoff (1 s). */\nconst BACKOFF_BASE_MS = 1_000;\n/** Maximum delay cap (30 s). */\nconst BACKOFF_MAX_MS = 30_000;\n\n/**\n * Classify a caught error and decide whether to retry, and how long to wait.\n * @param err The error thrown by api-client methods.\n * @param attempt 1-based attempt counter.\n * @returns A RetryDecision.\n */\nexport function classifyRetry(err: unknown, attempt: number): RetryDecision {\n if (attempt >= 3) return { retry: false, delayMs: 0 };\n\n if (err instanceof TelegramHttpError) {\n if (err.status === 429 || err.status === 409 || err.status >= 500) {\n const delayMs = Math.min(\n Math.ceil(BACKOFF_BASE_MS * 2 ** (attempt - 1) * (1 + Math.random() * 0.2)),\n BACKOFF_MAX_MS,\n );\n return { retry: true, delayMs };\n }\n return { retry: false, delayMs: 0 };\n }\n if (err instanceof TelegramResponseParseError) return { retry: false, delayMs: 0 };\n if (err instanceof TelegramNetworkError && err.aborted) return { retry: false, delayMs: 0 };\n\n if (err instanceof TelegramBotApiError) {\n const code = err.errorCode;\n if (code !== undefined && code >= 400 && code < 500 && code !== 429 && code !== 409) {\n return { retry: false, delayMs: 0 };\n }\n if (code === 429) {\n const baseDelay =\n err.retryAfterSeconds !== undefined\n ? err.retryAfterSeconds * 1000\n : BACKOFF_BASE_MS * 2 ** (attempt - 1);\n const delayMs = Math.min(Math.ceil(baseDelay * (1 + Math.random() * 0.3)), BACKOFF_MAX_MS);\n return { retry: true, delayMs };\n }\n if (code === 409) {\n const delayMs = Math.min(BACKOFF_BASE_MS * 2 ** (attempt - 1), BACKOFF_MAX_MS);\n return { retry: true, delayMs };\n }\n if (code !== undefined && code >= 500) {\n const delayMs = Math.min(\n Math.ceil(BACKOFF_BASE_MS * 2 ** (attempt - 1) * (1 + Math.random() * 0.2)),\n BACKOFF_MAX_MS,\n );\n return { retry: true, delayMs };\n }\n if (code === undefined) {\n const delayMs = Math.min(BACKOFF_BASE_MS * 2 ** (attempt - 1), BACKOFF_MAX_MS);\n return { retry: true, delayMs };\n }\n }\n\n const delayMs = Math.min(\n Math.ceil(BACKOFF_BASE_MS * 2 ** (attempt - 1) * (1 + Math.random() * 0.3)),\n BACKOFF_MAX_MS,\n );\n return { retry: true, delayMs };\n}\n\n// ---------------------------------------------------------------------------\n// Typed transport\n// ---------------------------------------------------------------------------\n\ntype TelegramFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\n\nexport interface TelegramApiClientOptions {\n token: string;\n /** Override used by deterministic tests or Bot API proxies. */\n apiRoot?: string | undefined;\n /** Optional transport injection. Defaults to globalThis.fetch at call time. */\n fetch?: TelegramFetch | undefined;\n}\n\nexport interface TelegramRequestOptions {\n signal?: AbortSignal | undefined;\n /** Optional parse_mode override for this call. */\n parseMode?: '' | 'HTML' | 'MarkdownV2' | undefined;\n}\n\nexport interface TelegramGetUpdatesOptions extends TelegramRequestOptions {\n deadlineMs?: number | undefined;\n\n offset: number;\n timeoutSeconds: number;\n}\n\n/** Build the one canonical, token-bearing Bot API base URL. Never log this value. */\nexport function buildTelegramBotApiBaseUrl(\n token: string,\n apiRoot = 'https://api.telegram.org',\n): string {\n return `${apiRoot.replace(/\\/+$/, '')}/bot${token}`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nexport function abortableSleep(ms: number, signal?: AbortSignal | undefined): Promise<void> {\n if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));\n return new Promise((resolve, reject) => {\n if (signal.aborted) {\n reject(new DOMException('The operation was aborted', 'AbortError'));\n return;\n }\n\n const cleanup = () => signal.removeEventListener('abort', onAbort);\n const timer = setTimeout(() => {\n cleanup();\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n cleanup();\n reject(new DOMException('The operation was aborted', 'AbortError'));\n };\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nfunction errorDetail(error: unknown): string {\n if (error instanceof Error) return error.message;\n return String(error);\n}\n\n/** Compose optional parent signal + deadline into one AbortSignal. Returns undefined when neither is set. */\nfunction composedSignal(\n signal?: AbortSignal | undefined,\n deadlineMs?: number | undefined,\n): AbortSignal | undefined {\n if (deadlineMs !== undefined && signal) {\n return AbortSignal.any([signal, AbortSignal.timeout(deadlineMs)]);\n }\n if (deadlineMs !== undefined) return AbortSignal.timeout(deadlineMs);\n return signal;\n}\n\nexport class TelegramApiClient {\n readonly safeBaseUrl: string;\n\n private readonly token: string;\n private readonly baseUrl: string;\n private readonly fetchOverride?: TelegramFetch | undefined;\n\n constructor(opts: TelegramApiClientOptions) {\n this.token = opts.token;\n this.baseUrl = buildTelegramBotApiBaseUrl(opts.token, opts.apiRoot);\n this.safeBaseUrl = this.redact(this.baseUrl);\n this.fetchOverride = opts.fetch;\n }\n\n getMe(opts?: TelegramRequestOptions): Promise<TelegramApiUser> {\n return this.request<TelegramApiUser>('getMe', { signal: composedSignal(opts?.signal) });\n }\n\n getUpdates(opts: TelegramGetUpdatesOptions): Promise<TelegramApiUpdate[]> {\n const query = new URLSearchParams({\n offset: String(opts.offset),\n timeout: String(opts.timeoutSeconds),\n });\n return this.request<TelegramApiUpdate[]>('getUpdates', {\n query,\n signal: composedSignal(opts.signal, opts.deadlineMs),\n });\n }\n\n sendMessage(\n chatId: string | number,\n text: string,\n opts?: TelegramRequestOptions,\n ): Promise<TelegramApiMessage> {\n const body: Record<string, unknown> = {\n chat_id: String(chatId),\n text,\n disable_web_page_preview: true,\n };\n if (opts?.parseMode) body.parse_mode = opts.parseMode;\n return this.request<TelegramApiMessage>('sendMessage', {\n body,\n signal: composedSignal(opts?.signal),\n });\n }\n\n sendMessageWithKeyboard(\n chatId: string | number,\n text: string,\n buttons: readonly TelegramInlineKeyboardButton[],\n opts?: TelegramRequestOptions,\n ): Promise<TelegramApiMessage> {\n const body: Record<string, unknown> = {\n chat_id: String(chatId),\n text,\n disable_web_page_preview: true,\n reply_markup: {\n inline_keyboard: [\n buttons.map((button) => ({\n text: button.text,\n callback_data: button.callback_data,\n })),\n ],\n },\n };\n if (opts?.parseMode) body.parse_mode = opts.parseMode;\n return this.request<TelegramApiMessage>('sendMessage', {\n body,\n signal: composedSignal(opts?.signal),\n });\n }\n\n answerCallbackQuery(\n callbackQueryId: string,\n text: string,\n showAlert: boolean,\n opts?: TelegramRequestOptions,\n ): Promise<boolean> {\n return this.request<boolean>('answerCallbackQuery', {\n body: {\n callback_query_id: callbackQueryId,\n text,\n show_alert: showAlert,\n },\n signal: composedSignal(opts?.signal),\n });\n }\n\n private async request<T>(\n method: string,\n opts?: {\n body?: Record<string, unknown> | undefined;\n query?: URLSearchParams | undefined;\n signal?: AbortSignal | undefined;\n },\n ): Promise<T> {\n const query = opts?.query?.toString();\n const url = `${this.baseUrl}/${method}${query ? `?${query}` : ''}`;\n const init: RequestInit = {\n method: opts?.body ? 'POST' : 'GET',\n };\n if (opts?.signal) init.signal = opts.signal;\n if (opts?.body) {\n init.headers = { 'Content-Type': 'application/json' };\n init.body = JSON.stringify(opts.body);\n }\n\n let response: Response;\n try {\n const fetchImpl = this.fetchOverride ?? globalThis.fetch;\n response = await fetchImpl(url, init);\n } catch (error) {\n const detail = this.redact(errorDetail(error));\n const aborted = error instanceof Error && error.name === 'AbortError';\n throw new TelegramNetworkError(method, detail, aborted);\n }\n\n let decoded: unknown;\n try {\n decoded = await response.json();\n } catch (error) {\n if (!response.ok) {\n throw new TelegramHttpError(method, response.status, this.redact(response.statusText));\n }\n throw new TelegramResponseParseError(method, this.redact(errorDetail(error)));\n }\n\n if (!isRecord(decoded) || typeof decoded.ok !== 'boolean') {\n if (!response.ok) {\n throw new TelegramHttpError(method, response.status, this.redact(response.statusText));\n }\n throw new TelegramResponseParseError(method, 'expected a Bot API response envelope');\n }\n\n const envelope = decoded as unknown as TelegramApiEnvelope<T>;\n if (!envelope.ok) {\n throw new TelegramBotApiError(method, {\n errorCode: envelope.error_code,\n httpStatus: response.status,\n description: this.redact(envelope.description ?? 'Unknown Bot API error'),\n retryAfterSeconds: envelope.parameters?.retry_after,\n migrateToChatId: envelope.parameters?.migrate_to_chat_id,\n });\n }\n if (!response.ok) {\n throw new TelegramHttpError(method, response.status, this.redact(response.statusText));\n }\n if (envelope.result === undefined || envelope.result === null) {\n throw new TelegramResponseParseError(method, 'successful response did not include result');\n }\n\n return envelope.result;\n }\n\n private redact(value: string): string {\n return value.replaceAll(this.token, '[REDACTED]');\n }\n}\n", "import type { Logger } from '@wrongstack/core/types';\nimport {\n TelegramApiClient,\n TelegramBotApiError,\n TelegramNetworkError,\n abortableSleep,\n classifyRetry,\n type TelegramApiCallbackQuery,\n type TelegramApiMessage,\n} from './api-client.js';\nimport type { OffsetStore } from './offset-store.js';\nimport type { PollLock } from './poll-lock.js';\n\nexport interface TelegramBotResponse<T> {\n ok: true;\n result: T;\n}\n\n// ---------------------------------------------------------------------------\n// Incoming message shape emitted as a custom event\n// ---------------------------------------------------------------------------\n\nexport interface TelegramIncomingMessage {\n messageId: number;\n chatId: number;\n chatType: string;\n userId?: number | undefined;\n userName?: string | undefined;\n text: string;\n timestamp: number;\n}\n\nexport interface TelegramApprovalResult {\n approved: boolean;\n fromUser: string;\n fromUserId?: number | undefined;\n}\n\nexport interface TelegramApprovalRequestInput {\n requestId: string;\n sessionId: string;\n expectedChatId: string | number;\n expectedUserIds: readonly (string | number)[];\n /** Group/supergroup callbacks are rejected unless this was explicitly enabled. */\n allowGroup: boolean;\n expiresAt: number;\n /** Cancels the request when its owning tool execution is aborted. */\n signal?: AbortSignal | undefined;\n}\n\ntype TelegramApprovalRequestState = 'pending' | 'resolved' | 'expired' | 'cancelled';\n\ninterface TelegramApprovalRequest {\n requestId: string;\n sessionId: string;\n expectedChatId: string;\n expectedUserIds: ReadonlySet<string>;\n allowGroup: boolean;\n promptMessageId?: number | undefined;\n pendingCallbacks: TelegramApiCallbackQuery[];\n expiresAt: number;\n state: TelegramApprovalRequestState;\n resolve: (value: TelegramApprovalResult) => void;\n timer: ReturnType<typeof setTimeout>;\n signal?: AbortSignal | undefined;\n abortHandler?: (() => void) | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Bot options\n// ---------------------------------------------------------------------------\n\nexport interface TelegramBotOptions {\n token: string;\n pollIntervalSec: number;\n allowedUsers: Set<string>;\n allowedChats: Set<string>;\n /** Max messages to buffer for the agent to read. Default: 50. */\n bufferSize: number;\n log: Logger;\n /**\n * Resolved on every outbound send so live `parseMode` config changes\n * (via `api.onConfigChange`) take effect without restarting the plugin.\n * Empty string or `undefined` \u2192 plain text. See `TelegramPluginConfig.parseMode`.\n */\n getParseMode?: () => '' | 'HTML' | 'MarkdownV2' | undefined;\n /** Called for each incoming message that passes allowlist checks. */\n onMessage(msg: TelegramIncomingMessage): void;\n /**\n * Optional typed offset store. When provided, the polling offset is persisted\n * atomically on every successful poll and restored on startup, preventing\n * message replay after crashes or restarts.\n */\n offsetStore?: OffsetStore | undefined;\n /**\n * Optional cross-process single-poller lock. Telegram allows one\n * `getUpdates` consumer per token; when another wstack instance holds the\n * lock, this bot stands by (no polling) and takes over once the holder\n * stops or its heartbeat goes stale.\n */\n lock?: PollLock | undefined;\n /** How often a standby instance retries acquiring the lock. Default: 15s. */\n standbyRetryMs?: number | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Bot\n// ---------------------------------------------------------------------------\n\nexport class TelegramBot {\n private readonly api: TelegramApiClient;\n private readonly pollIntervalMs: number;\n private readonly allowedUsers: Set<string>;\n private readonly allowedChats: Set<string>;\n private readonly log: Logger;\n private readonly onMessage: (msg: TelegramIncomingMessage) => void;\n private readonly controller = new AbortController();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private pollActive = false;\n private offset = 0;\n /**\n * Consecutive HTTP 409 (\"another getUpdates in flight\") responses. Two\n * wstack instances polling the same bot token used to fight at full poll\n * speed forever, erroring on every cycle. After CONFLICT_BACKOFF_AFTER\n * consecutive conflicts this instance backs off to a slow poll and warns\n * once; any successful poll resets to the normal cadence.\n */\n private conflictStreak = 0;\n private static readonly CONFLICT_BACKOFF_AFTER = 3;\n private static readonly CONFLICT_POLL_MS = 60_000;\n private _startedAt: number | null = null;\n /** Typed offset store for atomic polling-cursor persistence. */\n private readonly offsetStore?: OffsetStore | undefined;\n /** Single-poller election across wstack instances sharing this token. */\n private readonly lock?: PollLock | undefined;\n private readonly standbyRetryMs: number;\n private readonly getParseMode?: (() => '' | 'HTML' | 'MarkdownV2' | undefined) | undefined;\n private standbyTimer: ReturnType<typeof setTimeout> | null = null;\n private standbyAnnounced = false;\n\n // Circular buffer for incoming messages\n private readonly bufferMax: number;\n private readonly buffer: TelegramIncomingMessage[] = [];\n\n // Pending approval requests keyed by request identity, not raw callback\n // data. Each request binds both yes/no actions to its originating session,\n // target chat, intended users, prompt message, and expiry.\n private readonly callbackWaiters = new Map<string, TelegramApprovalRequest>();\n\n constructor(opts: TelegramBotOptions) {\n this.api = new TelegramApiClient({ token: opts.token });\n this.pollIntervalMs = opts.pollIntervalSec * 1000;\n this.allowedUsers = opts.allowedUsers;\n this.allowedChats = opts.allowedChats;\n this.bufferMax = opts.bufferSize;\n this.log = opts.log;\n this.onMessage = opts.onMessage;\n this.offsetStore = opts.offsetStore;\n this.lock = opts.lock;\n this.standbyRetryMs = opts.standbyRetryMs ?? 15_000;\n this.getParseMode = opts.getParseMode;\n if (this.lock) {\n this.lock.onLost = () => this.handleLockLost();\n }\n\n // Restore persisted offset so a crash/restart doesn't cause message replay.\n if (this.offsetStore) {\n void this.loadOffset();\n }\n }\n\n // ------------------------------------------------------------------\n // Lifecycle\n // ------------------------------------------------------------------\n\n /** Start polling for updates. Idempotent. */\n start(): void {\n if (this.pollActive) return;\n this.pollActive = true;\n this._startedAt = Date.now();\n this.acquireAndPoll();\n }\n\n /** Stop polling and cancel all in-flight requests. */\n stop(): void {\n this.pollActive = false;\n this.controller.abort();\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n if (this.standbyTimer) {\n clearTimeout(this.standbyTimer);\n this.standbyTimer = null;\n }\n // Reject any pending approval requests so the host doesn't hang.\n for (const requestId of Array.from(this.callbackWaiters.keys())) {\n this.settleApproval(requestId, 'cancelled', {\n approved: false,\n fromUser: 'shutdown',\n });\n }\n this.lock?.release();\n this.log.info('Telegram bot stopped');\n }\n\n /** True when the bot is started but waiting for the poll lock. */\n get standby(): boolean {\n return this.pollActive && this.lock !== undefined && !this.lock.held;\n }\n\n /**\n * Acquire the poll lock (when configured) and start the poll loop, or\n * stand by and retry until the current holder releases it.\n */\n private acquireAndPoll(): void {\n if (!this.pollActive) return;\n if (this.lock && !this.lock.tryAcquire()) {\n if (!this.standbyAnnounced) {\n this.standbyAnnounced = true;\n this.log.info(\n 'Telegram: another wstack instance is already polling this bot token \u2014 standing by; will take over when it stops.',\n );\n }\n this.standbyTimer = setTimeout(() => this.acquireAndPoll(), this.standbyRetryMs);\n this.standbyTimer.unref?.();\n return;\n }\n if (this.standbyAnnounced) {\n this.standbyAnnounced = false;\n this.log.info('Telegram: poll lock acquired \u2014 taking over polling.');\n } else {\n this.log.info(`Telegram bot polling started (${this.api.safeBaseUrl})`);\n }\n this.schedulePoll();\n }\n\n /** The lock was stolen while we held it \u2014 pause polling and stand by. */\n private handleLockLost(): void {\n if (!this.pollActive) return;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n this.log.warn(\n 'Telegram: poll lock lost to another instance \u2014 pausing polling and standing by.',\n );\n this.standbyAnnounced = true; // acquireAndPoll already announced via this warn\n this.standbyTimer = setTimeout(() => this.acquireAndPoll(), this.standbyRetryMs);\n this.standbyTimer.unref?.();\n }\n\n get startedAt(): number | null {\n return this._startedAt;\n }\n\n get running(): boolean {\n return this.pollActive;\n }\n\n // ------------------------------------------------------------------\n // Buffer \u2014 incoming messages the agent can read\n // ------------------------------------------------------------------\n\n /** Return buffered messages, newest first. Optionally filter by chat. */\n getMessages(opts?: {\n chatId?: string | number | undefined;\n limit?: number | undefined;\n }): TelegramIncomingMessage[] {\n let msgs = [...this.buffer].reverse();\n if (opts?.chatId) {\n const cid = String(opts.chatId);\n msgs = msgs.filter((m) => String(m.chatId) === cid);\n }\n const limit = opts?.limit ?? 20;\n return msgs.slice(0, limit);\n }\n\n /** Drop messages older than the given message ID from the buffer. */\n acknowledge(lastMessageId: number): number {\n const before = this.buffer.length;\n let i = this.buffer.length;\n while (i-- > 0) {\n const buffered = this.buffer[i];\n if (buffered && buffered.messageId <= lastMessageId) {\n this.buffer.splice(0, i + 1);\n break;\n }\n }\n return before - this.buffer.length;\n }\n\n get bufferCount(): number {\n return this.buffer.length;\n }\n\n // ------------------------------------------------------------------\n // Outgoing \u2014 send a message\n // ------------------------------------------------------------------\n\n async sendMessage(\n chatId: string | number,\n text: string,\n signal?: AbortSignal | undefined,\n ): Promise<TelegramBotResponse<TelegramApiMessage>> {\n this.log.debug(`Sending Telegram message to ${chatId} (${text.length} chars)`);\n\n let lastErr: unknown;\n for (let attempt = 1; attempt <= 3; attempt++) {\n try {\n const timeout = AbortSignal.timeout(10_000);\n const result = await this.api.sendMessage(chatId, text, {\n signal: signal ? AbortSignal.any([signal, timeout]) : timeout,\n parseMode: this.getParseMode?.(),\n });\n return { ok: true, result };\n } catch (err) {\n lastErr = err;\n const decision = classifyRetry(err, attempt);\n if (!decision.retry) {\n if (attempt > 1)\n this.log.debug(\n `Telegram sendMessage terminal error on attempt ${attempt}, not retrying`,\n );\n break;\n }\n this.log.debug(\n `Telegram sendMessage attempt ${attempt} failed, retrying in ${decision.delayMs}ms...`,\n );\n await abortableSleep(decision.delayMs, signal);\n }\n }\n throw lastErr;\n }\n\n // ------------------------------------------------------------------\n // Outgoing \u2014 send a message with an inline keyboard\n // ------------------------------------------------------------------\n\n /**\n * Send a message that has up to one row of inline buttons (Telegram's\n * `inline_keyboard`). Used by `telegram_approve` to present a\n * yes/no prompt. The keyboard payload is opaque to the bot \u2014 callers\n * pass already-encoded `callback_data` strings (\u2264 64 bytes each).\n */\n async sendMessageWithKeyboard(\n chatId: string | number,\n text: string,\n buttons: Array<{ text: string; callback_data: string }>,\n signal?: AbortSignal | undefined,\n ): Promise<TelegramBotResponse<TelegramApiMessage>> {\n let lastErr: unknown;\n for (let attempt = 1; attempt <= 3; attempt++) {\n try {\n const timeout = AbortSignal.timeout(10_000);\n const result = await this.api.sendMessageWithKeyboard(chatId, text, buttons, {\n signal: signal ? AbortSignal.any([signal, timeout]) : timeout,\n parseMode: this.getParseMode?.(),\n });\n return { ok: true, result };\n } catch (err) {\n lastErr = err;\n const decision = classifyRetry(err, attempt);\n if (!decision.retry) {\n if (attempt > 1)\n this.log.debug(\n `Telegram sendMessageWithKeyboard terminal error on attempt ${attempt}, not retrying`,\n );\n break;\n }\n await abortableSleep(decision.delayMs, signal);\n }\n }\n throw lastErr;\n }\n\n // ------------------------------------------------------------------\n // Health\n // ------------------------------------------------------------------\n\n async health(signal?: AbortSignal | undefined): Promise<{\n ok: boolean;\n username?: string | undefined;\n error?: string | undefined;\n }> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), 5000);\n try {\n const timeout = AbortSignal.timeout(5_000);\n const deadline = AbortSignal.any([ctrl.signal, timeout]);\n const combined = signal ? AbortSignal.any([signal, deadline]) : deadline;\n const user = await this.api.getMe({ signal: combined });\n return { ok: true, username: user.username };\n } catch (err) {\n if (err instanceof TelegramBotApiError) return { ok: false, error: err.description };\n if (err instanceof TelegramNetworkError) return { ok: false, error: err.detail };\n return { ok: false, error: (err as Error).message };\n } finally {\n clearTimeout(timer);\n }\n }\n\n // ------------------------------------------------------------------\n // Polling\n // ------------------------------------------------------------------\n\n private schedulePoll(): void {\n if (!this.pollActive) return;\n // Lost the poll lock mid-flight \u2014 the standby retry loop owns recovery.\n if (this.lock && !this.lock.held) return;\n const delay =\n this.conflictStreak >= TelegramBot.CONFLICT_BACKOFF_AFTER\n ? TelegramBot.CONFLICT_POLL_MS\n : this.pollIntervalMs;\n this.pollTimer = setTimeout(() => {\n void this.poll().finally(() => this.schedulePoll());\n }, delay);\n }\n\n private async poll(): Promise<void> {\n try {\n const updates = await this.api.getUpdates({\n offset: this.offset,\n timeoutSeconds: 10,\n deadlineMs: 15_000,\n signal: this.controller.signal,\n });\n this.conflictStreak = 0;\n\n for (const upd of updates) {\n this.offset = upd.update_id + 1;\n if (upd.callback_query) {\n void this.dispatchCallback(upd.callback_query).catch((err) =>\n this.log.debug(\n `Callback dispatch failed: ${err instanceof Error ? err.message : String(err)}`,\n ),\n );\n continue;\n }\n\n const raw = upd.message ?? upd.edited_message;\n if (!raw?.text) continue;\n this.processMessage({ ...raw, text: raw.text });\n }\n\n // P1.6: commit the cursor only after processing. An empty poll or a\n // 0 -> 0 idle tick MUST NOT trigger a write. Require updates.length > 0\n // so a successful but empty poll leaves the persisted offset\n // unchanged \u2014 preserves the replay dedup boundary on restart.\n if (this.offsetStore && updates.length > 0) void this.saveOffset();\n } catch (err) {\n if (err instanceof TelegramNetworkError && err.aborted) return;\n if (err instanceof TelegramBotApiError && err.errorCode === 409) {\n this.conflictStreak++;\n if (this.conflictStreak === TelegramBot.CONFLICT_BACKOFF_AFTER) {\n this.log.warn(\n this.lock\n ? 'Telegram: another consumer outside this machine is polling this bot token (HTTP 409) \u2014 backing off to 60s polls. Check other machines/bots using this token, or a registered webhook (deleteWebhook).'\n : 'Telegram: another instance is polling this bot token (HTTP 409) \u2014 backing off to 60s polls until it stops.',\n );\n }\n this.log.debug(`Telegram getUpdates failed: ${err.description}`);\n return;\n }\n this.log.debug(`Telegram poll error: ${(err as Error).message}`);\n }\n }\n\n /**\n * Apply the inbound identity policy to every update type. A non-empty set is\n * a mandatory constraint: missing identity fails closed instead of bypassing\n * the allowlist. An empty set leaves that identity dimension unrestricted.\n */\n private inboundDenialReason(\n userId: string | undefined,\n chatId: string | undefined,\n ): 'user' | 'chat' | undefined {\n // Check the chat first so a doubly-blocked message cannot trigger an\n // unauthorized-user reply into an arbitrary, non-allowlisted chat.\n if (this.allowedChats.size > 0 && (chatId === undefined || !this.allowedChats.has(chatId))) {\n return 'chat';\n }\n if (this.allowedUsers.size > 0 && (userId === undefined || !this.allowedUsers.has(userId))) {\n return 'user';\n }\n return undefined;\n }\n\n private processMessage(msg: TelegramApiMessage & { text: string }): void {\n const chatId = String(msg.chat.id);\n const userId = msg.from ? String(msg.from.id) : undefined;\n const denialReason = this.inboundDenialReason(userId, chatId);\n\n if (denialReason === 'user') {\n this.log.debug(`Ignoring message from user ${userId ?? 'unknown'} (not in allowedUsers)`);\n // Best-effort denial notice: the reply itself can fail (bot blocked, chat\n // not found \u2192 non-retryable throw). Swallow so an unauthorized user\n // cannot spam a stream of unhandled rejections into the poll loop.\n void this.sendMessage(chatId, '\u26D4 You are not authorized to interact with this bot.').catch(\n (err) =>\n this.log.debug(\n `Failed to send denial notice: ${err instanceof Error ? err.message : String(err)}`,\n ),\n );\n return;\n }\n if (denialReason === 'chat') {\n this.log.debug(`Ignoring message from chat ${chatId} (not in allowedChats)`);\n return;\n }\n\n const incoming: TelegramIncomingMessage = {\n messageId: msg.message_id,\n chatId: msg.chat.id,\n chatType: msg.chat.type,\n userId: msg.from?.id,\n userName: msg.from?.username ?? msg.from?.first_name,\n text: msg.text,\n timestamp: msg.date * 1000,\n };\n\n // Push to circular buffer\n this.buffer.push(incoming);\n while (this.buffer.length > this.bufferMax) this.buffer.shift();\n\n this.onMessage(incoming);\n }\n\n /**\n * Resolve a pending approval request exactly once and record its terminal\n * state before removing it from the live registry.\n */\n private settleApproval(\n requestId: string,\n state: Exclude<TelegramApprovalRequestState, 'pending'>,\n result: TelegramApprovalResult,\n ): boolean {\n const request = this.callbackWaiters.get(requestId);\n if (request?.state !== 'pending') return false;\n request.state = state;\n clearTimeout(request.timer);\n if (request.signal && request.abortHandler) {\n request.signal.removeEventListener('abort', request.abortHandler);\n }\n request.pendingCallbacks.length = 0;\n this.callbackWaiters.delete(requestId);\n request.resolve(result);\n return true;\n }\n\n private async dispatchCallback(cq: TelegramApiCallbackQuery): Promise<void> {\n const key = cq.data ?? '';\n const action = /^approve:([^:]+):(yes|no)$/.exec(key);\n const requestId = action?.[1];\n const request = requestId ? this.callbackWaiters.get(requestId) : undefined;\n\n // Use the same coarse inbound policy as messages before applying the\n // request-specific identity binding below. Unauthorized callbacks are\n // acknowledged but never consume the valid user's pending request.\n const userId = cq.from?.id !== undefined ? String(cq.from.id) : undefined;\n const chatId = cq.message?.chat.id !== undefined ? String(cq.message.chat.id) : undefined;\n const denialReason = this.inboundDenialReason(userId, chatId);\n if (denialReason) {\n const identity = denialReason === 'user' ? (userId ?? 'unknown') : (chatId ?? 'unknown');\n this.log.warn(\n `Ignoring callback_query from non-allowlisted ${denialReason} ${identity} (data=\"${key}\") \u2014 possible hijack attempt.`,\n );\n await this.answerCallback(cq.id, '\u26D4 Not authorized', true);\n return;\n }\n\n if (!request || !requestId || !action) {\n await this.answerCallback(cq.id, 'Approval request unavailable', true);\n this.log.debug(`Unmatched callback_query data=\"${key}\" (no pending approval request)`);\n return;\n }\n\n if (Date.now() >= request.expiresAt) {\n await this.answerCallback(cq.id, 'Approval request expired', true);\n this.settleApproval(requestId, 'expired', { approved: false, fromUser: 'timeout' });\n return;\n }\n\n // The request is registered before sendMessage so a callback can arrive\n // before the Bot API response supplies message_id. Keep exactly that\n // callback queued until bindApprovalPrompt attaches the sent prompt.\n if (request.promptMessageId === undefined) {\n request.pendingCallbacks.push(cq);\n return;\n }\n\n const messageId = cq.message?.message_id;\n const chatType = cq.message?.chat.type;\n const wrongIdentity =\n userId === undefined ||\n chatId !== request.expectedChatId ||\n !request.expectedUserIds.has(userId) ||\n messageId !== request.promptMessageId ||\n (chatType !== 'private' && !request.allowGroup);\n if (wrongIdentity) {\n this.log.warn(\n `Ignoring callback_query that does not match approval request ${request.requestId} in session ${request.sessionId}.`,\n );\n await this.answerCallback(cq.id, '\u26D4 Not authorized for this approval', true);\n return;\n }\n\n const approved = action[2] === 'yes';\n const fromUser = cq.from?.username ?? cq.from?.first_name ?? `user:${userId}`;\n const resolved = this.settleApproval(requestId, 'resolved', {\n approved,\n fromUser,\n fromUserId: cq.from?.id,\n });\n await this.answerCallback(\n cq.id,\n resolved ? (approved ? 'Approved \u2713' : 'Denied \u2717') : 'Approval request unavailable',\n !resolved,\n );\n }\n\n /**\n * POST /answerCallbackQuery for a callback. Best-effort: failures are\n * logged at debug and swallowed \u2014 the caller's resolve() must not depend\n * on the ack reaching Telegram (the user may get a \"loading\" spinner if\n * it fails, but the agent's approval flow continues normally).\n */\n private async answerCallback(\n callbackQueryId: string,\n text: string,\n showAlert: boolean,\n ): Promise<void> {\n try {\n await this.api.answerCallbackQuery(callbackQueryId, text, showAlert, {\n signal: AbortSignal.timeout(5_000),\n });\n } catch (err) {\n this.log.debug(`answerCallbackQuery failed: ${(err as Error).message}`);\n }\n }\n\n /**\n * Register one approval request before its prompt is sent. The returned\n * promise owns the request's only timer and resolves on one terminal event.\n */\n awaitApproval(input: TelegramApprovalRequestInput): Promise<TelegramApprovalResult> {\n if (input.expectedUserIds.length === 0) {\n throw new Error('Telegram approval requires at least one expected user ID.');\n }\n if (this.callbackWaiters.has(input.requestId)) {\n throw new Error(`Telegram approval request ${input.requestId} is already pending.`);\n }\n\n return new Promise((resolve) => {\n const delayMs = Math.max(0, input.expiresAt - Date.now());\n const timer = setTimeout(() => {\n this.settleApproval(input.requestId, 'expired', {\n approved: false,\n fromUser: 'timeout',\n });\n }, delayMs);\n const request: TelegramApprovalRequest = {\n requestId: input.requestId,\n sessionId: input.sessionId,\n expectedChatId: String(input.expectedChatId),\n expectedUserIds: new Set(input.expectedUserIds.map(String)),\n allowGroup: input.allowGroup,\n pendingCallbacks: [],\n expiresAt: input.expiresAt,\n state: 'pending',\n resolve,\n timer,\n signal: input.signal,\n };\n if (input.signal) {\n request.abortHandler = () => {\n this.settleApproval(input.requestId, 'cancelled', {\n approved: false,\n fromUser: 'aborted',\n });\n };\n }\n this.callbackWaiters.set(input.requestId, request);\n if (input.signal?.aborted) {\n request.abortHandler?.();\n } else if (input.signal && request.abortHandler) {\n input.signal.addEventListener('abort', request.abortHandler, { once: true });\n }\n });\n }\n\n /**\n * Attach the Bot API response's prompt message ID to an existing request.\n * Any callback that arrived during the send is replayed against the fully\n * bound identity without allocating a second waiter or timer.\n */\n bindApprovalPrompt(requestId: string, promptMessageId: number): boolean {\n const request = this.callbackWaiters.get(requestId);\n if (request?.state !== 'pending' || request.promptMessageId !== undefined) return false;\n request.promptMessageId = promptMessageId;\n const pending = request.pendingCallbacks.splice(0);\n for (const callback of pending) {\n void this.dispatchCallback(callback).catch((err) =>\n this.log.debug(\n `Callback dispatch failed: ${err instanceof Error ? err.message : String(err)}`,\n ),\n );\n }\n return true;\n }\n\n /** Cancel a request that cannot reach a valid terminal callback. */\n cancelApproval(requestId: string, fromUser = 'cancelled'): boolean {\n return this.settleApproval(requestId, 'cancelled', { approved: false, fromUser });\n }\n\n private async loadOffset(): Promise<void> {\n if (!this.offsetStore) return;\n try {\n const saved = this.offsetStore.read();\n if (saved !== null) {\n this.offset = saved;\n this.log.debug(`Telegram polling offset restored: ${this.offset}`);\n }\n } catch {\n // Best-effort \u2014 a corrupt or missing file starts from 0.\n }\n }\n\n private async saveOffset(): Promise<void> {\n if (!this.offsetStore) return;\n try {\n this.offsetStore.write(this.offset);\n } catch (err) {\n this.log.debug(`Failed to persist Telegram offset: ${err}`);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Truncate text to fit Telegram's 4096-char message limit.\n * Preserves semantic boundaries in this priority order:\n * 1. Paragraph break (double newline)\n * 2. Sentence break (. ! ? followed by space/newline)\n * 3. Word break (space)\n * 4. Hard cut with ellipsis\n *\n * When a clean boundary is found, appends \"\u2026\" to signal intentional truncation.\n */\n/**\n * Maximum permitted output length. Telegram rejects messages longer than\n * 4096 characters; we clamp the requested cap at this value so a\n * misconfigured caller cannot silently violate the platform contract.\n */\nconst MAX_TELEGRAM_MESSAGE_LENGTH = 4096;\n\nexport function truncateForTelegram(text: string, maxLen = 4000): string {\n // P1.8 explicit message-length contract: the caller's cap is the\n // binding contract, but it is clamped at Telegram's hard 4096-char\n // limit so a misconfigured `maxLen > 4096` cannot silently produce\n // output that the platform will reject.\n const effectiveMaxLen = Math.min(maxLen, MAX_TELEGRAM_MESSAGE_LENGTH);\n if (text.length <= effectiveMaxLen) return text;\n\n // Reserve room for truncation suffix\n const cutoff = effectiveMaxLen - 30;\n if (cutoff <= 0) return `${text.slice(0, effectiveMaxLen - 1)}\u2026`;\n\n const searchEnd = Math.min(text.length, effectiveMaxLen);\n\n // 1. Paragraph boundary (double newline)\n const paraIdx = text.lastIndexOf('\\n\\n', searchEnd);\n if (paraIdx > cutoff) {\n return `${text.slice(0, paraIdx)}\\n\\n\u2026`;\n }\n\n // 2. Single newline boundary\n const nlIdx = text.lastIndexOf('\\n', searchEnd);\n if (nlIdx > cutoff) {\n return `${text.slice(0, nlIdx)}\\n\u2026`;\n }\n\n // 3. Sentence boundary (. ! ? followed by space or newline)\n const sentenceRe = /[.!?](?=\\s)/g;\n let match: RegExpExecArray | null;\n let sentenceIdx = -1;\n match = sentenceRe.exec(text);\n while (match !== null) {\n if (match.index >= searchEnd) break;\n if (match.index > cutoff) sentenceIdx = match.index + 1;\n match = sentenceRe.exec(text);\n }\n if (sentenceIdx > cutoff) {\n return `${text.slice(0, sentenceIdx)}\u2026`;\n }\n\n // 4. Word boundary (space)\n const spaceIdx = text.lastIndexOf(' ', searchEnd);\n if (spaceIdx > cutoff) {\n return `${text.slice(0, spaceIdx)} \u2026`;\n }\n\n // 5. Hard cut\n return `${text.slice(0, effectiveMaxLen - 20)}\u2026[+${text.length - effectiveMaxLen + 20} chars]`;\n}\n\n/**\n * Escape HTML special chars for Telegram's HTML parse mode.\n */\nexport function escapeHtml(text: string): string {\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n", "import { type PluginAPI, resolvePluginConfig } from '@wrongstack/core/plugin';\nimport type { Config, PluginConfigFields } from '@wrongstack/core/types';\n\nexport const PLUGIN_NAME = 'telegram';\nexport const PLUGIN_CONFIG_ALIASES = ['@wrongstack/telegram'] as const;\n\nexport type TelegramInboundMode = 'disabled' | 'paired' | 'allowlist' | 'public';\n\nconst INBOUND_MODES = ['disabled', 'paired', 'allowlist', 'public'] as const;\n\nexport interface TelegramPluginConfig {\n /** Telegram Bot API token (from @BotFather). */\n botToken: string;\n /**\n * Default chat ID for outgoing notifications.\n * The agent's `telegram_send` tool can override per-call.\n */\n notifyChatId?: string | number | undefined;\n /**\n * Controls who may send inbound messages to the bot. Defaults to `disabled`\n * for new/unpaired configurations. Legacy configurations are migrated to\n * `allowlist` when IDs exist, or `paired` when `notifyChatId` exists.\n */\n inboundMode?: TelegramInboundMode | undefined;\n /** List of user IDs accepted when `inboundMode` is `allowlist`. */\n allowedUsers?: Array<string | number> | undefined;\n /** List of chat IDs accepted when `inboundMode` is `allowlist`. */\n allowedChats?: Array<string | number> | undefined;\n /** Additional trusted targets for outbound sends beyond `notifyChatId`. */\n allowedOutboundChats?: Array<string | number> | undefined;\n /** Polling interval in seconds (default: 2). */\n pollIntervalSec?: number | undefined;\n /** Notify on Telegram when a session ends. */\n notifyOnSessionEnd?: boolean | undefined;\n /** Notify when a tool runs longer than this threshold (ms). Set 0 to disable. */\n longToolThresholdMs?: number | undefined;\n /** Notify (humanized) when a `delegate` subagent finishes. Default: true. */\n notifyOnDelegate?: boolean | undefined;\n /** Maximum message length for Telegram (Telegram caps at 4096). */\n maxMessageLength?: number | undefined;\n /**\n * Path to a file that stores the Telegram polling offset. When set,\n * the offset is persisted on every successful poll and restored on startup,\n * preventing message replay after crashes or restarts.\n * The directory must already exist and be writable.\n */\n offsetStoragePath?: string | undefined;\n /**\n * Elect a single poller per bot token across wstack instances (default:\n * true). Telegram allows one `getUpdates` consumer per token; without this,\n * two instances sharing a token fight and get HTTP 409 on every poll.\n * Extra instances stand by and take over when the active poller stops.\n * Set false only if this is guaranteed to be the sole consumer.\n */\n singleInstanceLock?: boolean | undefined;\n /**\n * Per-chat pending-message cap for the outbound queue. Older pending\n * notification entries are dropped when this is exceeded; manual\n * telegram_send entries surface the overflow as an error. Default: 32.\n */\n outboundQueuePerChat?: number | undefined;\n /** Maximum concurrent outbound sends across all chats. Default: 4. */\n outboundQueueConcurrency?: number | undefined;\n /** Permit group-chat approvals only when an explicit user allowlist also matches. */\n allowGroupApprovals?: boolean | undefined;\n /** Per-chat rate limit (tokens per second). Default: 0.33 (\u224820 msg/min). */\n rateLimitTokensPerSecond?: number | undefined;\n /** Per-chat rate limit burst size. Default: 4. */\n rateLimitBurst?: number | undefined;\n /**\n * Telegram parse mode for message text formatting. Supports:\n * - `'HTML'` \u2014 `<b>bold</b>`, `<i>italic</i>`, `<a href=\"...\">link</a>`, `<code>mono</code>`, `<pre>code block</pre>`\n * - `'MarkdownV2'` \u2014 `*bold*`, `_italic_`, `[link](url)`, `` `code` ``, ```pre```\n * - unset / `''` \u2014 plain text (no formatting)\n */\n parseMode?: '' | 'HTML' | 'MarkdownV2' | undefined;\n}\n\nexport const DEFAULT_CONFIG: Required<\n Omit<TelegramPluginConfig, 'botToken' | 'notifyChatId' | 'offsetStoragePath'>\n> = {\n inboundMode: 'disabled',\n allowedUsers: [],\n allowedChats: [],\n allowedOutboundChats: [],\n pollIntervalSec: 2,\n notifyOnSessionEnd: false,\n longToolThresholdMs: 30_000,\n notifyOnDelegate: true,\n maxMessageLength: 4000,\n singleInstanceLock: true,\n outboundQueuePerChat: 32,\n outboundQueueConcurrency: 4,\n allowGroupApprovals: false,\n rateLimitTokensPerSecond: 0.33,\n rateLimitBurst: 4,\n parseMode: '',\n};\n\nexport const TELEGRAM_CONFIG_FIELDS = {\n botToken: { lifecycle: 'restart', secret: true },\n notifyChatId: { lifecycle: 'restart' },\n inboundMode: { lifecycle: 'hot' },\n allowedUsers: { lifecycle: 'hot' },\n allowedChats: { lifecycle: 'hot' },\n allowedOutboundChats: { lifecycle: 'hot' },\n allowGroupApprovals: { lifecycle: 'hot' },\n pollIntervalSec: { lifecycle: 'hot' },\n notifyOnSessionEnd: { lifecycle: 'hot' },\n longToolThresholdMs: { lifecycle: 'hot' },\n notifyOnDelegate: { lifecycle: 'hot' },\n maxMessageLength: { lifecycle: 'hot' },\n offsetStoragePath: { lifecycle: 'immutable' },\n singleInstanceLock: { lifecycle: 'restart' },\n outboundQueuePerChat: { lifecycle: 'restart' },\n outboundQueueConcurrency: { lifecycle: 'restart' },\n rateLimitTokensPerSecond: { lifecycle: 'hot', description: 'Per-chat rate limit (tokens/sec)' },\n rateLimitBurst: { lifecycle: 'hot', description: 'Per-chat rate limit burst size' },\n parseMode: { lifecycle: 'hot', description: 'Telegram parse mode: HTML, MarkdownV2, or empty for plain text' },\n} as const satisfies PluginConfigFields<TelegramPluginConfig>;\n\nexport const telegramConfigSchema = {\n type: 'object',\n properties: {\n botToken: { type: 'string', description: 'Telegram Bot API token from @BotFather' },\n notifyChatId: {\n oneOf: [{ type: 'string' }, { type: 'integer' }],\n description: 'Default chat ID for outgoing notifications',\n },\n inboundMode: {\n type: 'string',\n enum: [...INBOUND_MODES],\n default: 'disabled',\n description:\n 'Inbound access: disabled, paired to notifyChatId, restricted by allowlists, or explicitly public',\n },\n allowedUsers: {\n type: 'array',\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\n description: 'User IDs accepted when inboundMode is allowlist',\n },\n allowedChats: {\n type: 'array',\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\n description: 'Chat IDs accepted when inboundMode is allowlist',\n },\n allowedOutboundChats: {\n type: 'array',\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\n description: 'Additional trusted targets for outbound Telegram sends',\n },\n pollIntervalSec: {\n type: 'integer',\n minimum: 1,\n maximum: 60,\n description: 'Polling interval in seconds',\n },\n notifyOnSessionEnd: { type: 'boolean' },\n longToolThresholdMs: { type: 'integer', minimum: 0 },\n notifyOnDelegate: { type: 'boolean' },\n maxMessageLength: { type: 'integer', minimum: 100, maximum: 4096 },\n offsetStoragePath: { type: 'string' },\n singleInstanceLock: {\n type: 'boolean',\n description:\n 'Elect a single getUpdates poller per bot token across wstack instances (default true)',\n },\n outboundQueuePerChat: {\n type: 'integer',\n minimum: 1,\n maximum: 1000,\n description: 'Per-chat pending outbound-message cap (default 32)',\n },\n outboundQueueConcurrency: {\n type: 'integer',\n minimum: 1,\n maximum: 64,\n description: 'Maximum concurrent outbound sends across all chats (default 4)',\n },\n allowGroupApprovals: { type: 'boolean' },\n rateLimitTokensPerSecond: {\n type: 'number',\n minimum: 0.1,\n maximum: 100,\n description: 'Per-chat rate limit in tokens per second (default: 0.33 \u224820 msg/min)',\n },\n rateLimitBurst: {\n type: 'integer',\n minimum: 1,\n maximum: 100,\n description: 'Per-chat burst size (default: 1)',\n },\n parseMode: {\n type: 'string',\n enum: ['', 'HTML', 'MarkdownV2'],\n description: 'Telegram parse mode: HTML, MarkdownV2, or empty for plain text',\n },\n },\n required: ['botToken'],\n};\n\nexport function readTelegramConfig(\n api: Pick<PluginAPI, 'config'> & Partial<Pick<PluginAPI, 'log'>>,\n): Required<Omit<TelegramPluginConfig, 'notifyChatId' | 'offsetStoragePath'>> &\n Pick<TelegramPluginConfig, 'notifyChatId' | 'offsetStoragePath'> {\n const resolution = resolvePluginConfig({\n name: PLUGIN_NAME,\n aliases: PLUGIN_CONFIG_ALIASES,\n config: api.config,\n });\n const opts = resolution.options as unknown as TelegramPluginConfig;\n const inboundMode = resolveInboundMode(opts, {\n configured: resolution.configured,\n warn: api.log?.warn.bind(api.log),\n });\n\n return {\n ...DEFAULT_CONFIG,\n ...opts,\n inboundMode,\n };\n}\n\n/**\n * Read the Telegram config section from a raw `Config` snapshot.\n * Used by `api.onConfigChange` callbacks that receive `(next: Config, prev: Config)`\n * rather than a `PluginAPI`. Delegates to {@link readTelegramConfig} so the\n * merge logic (legacy plugins, extension opts, inbound-mode resolution,\n * defaults) stays in one place.\n */\nexport function readTelegramConfigFromConfig(cfg: Config): TelegramPluginConfig {\n return readTelegramConfig({ config: cfg });\n}\n\nfunction resolveInboundMode(\n opts: TelegramPluginConfig,\n migration: { configured: boolean; warn?: ((message: string) => void) | undefined },\n): TelegramInboundMode {\n if (opts.inboundMode !== undefined) {\n if (!INBOUND_MODES.includes(opts.inboundMode)) {\n throw new Error(\n `Invalid telegram inboundMode \"${String(opts.inboundMode)}\". Expected one of: ${INBOUND_MODES.join(', ')}.`,\n );\n }\n if (\n opts.inboundMode === 'allowlist' &&\n !hasEntries(opts.allowedUsers) &&\n !hasEntries(opts.allowedChats)\n ) {\n throw new Error(\n 'Telegram inboundMode \"allowlist\" requires at least one allowedUsers or allowedChats entry.',\n );\n }\n if (opts.inboundMode === 'paired' && opts.notifyChatId === undefined) {\n throw new Error('Telegram inboundMode \"paired\" requires notifyChatId.');\n }\n return opts.inboundMode;\n }\n\n if (hasEntries(opts.allowedUsers) || hasEntries(opts.allowedChats)) return 'allowlist';\n\n const inferredMode: TelegramInboundMode = opts.notifyChatId === undefined ? 'disabled' : 'paired';\n if (migration.configured) {\n migration.warn?.(\n `Telegram inbound access no longer defaults to public when allowedUsers and allowedChats are empty; inferred inboundMode \"${inferredMode}\". Set inboundMode \"public\" explicitly to preserve legacy allow-all behavior.`,\n );\n }\n return inferredMode;\n}\n\nfunction hasEntries(values: Array<string | number> | undefined): boolean {\n return Array.isArray(values) && values.length > 0;\n}\n", "// ---------------------------------------------------------------------------\n// Secret redaction for outbound Telegram messages.\n//\n// Mirrors `redactCommand` from `@wrongstack/tools` (process-registry.ts:66)\n// without taking a dependency on the tools package. The regex set is the\n// same one used by `bash`/`exec`/`_spawn-stream` to redact session JSONL,\n// crash dumps, and `/ps` output. The Telegram notification path is the\n// highest-risk exfiltration surface \u2014 tool output printed by a long bash\n// run is forwarded verbatim to a phone notification \u2014 so we run every\n// outgoing payload through this filter.\n//\n// This file is intentionally tiny and dependency-free so it can be unit\n// tested in isolation and lifted into `@wrongstack/core/utils` later if\n// more plugins need it.\n// ---------------------------------------------------------------------------\n\n// Patterns match the flag/value or env-var/secret pair. The replacement\n// callback preserves the flag name and replaces only the value, so the\n// output still reads naturally (\"--token=[REDACTED]\") and downstream\n// debugging is not destroyed.\nconst SENSITIVE_FLAG_PATTERNS: RegExp[] = [\n // --flag=value or --flag \"value\" (value captured up to next space/comma)\n /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token|database[-_]?url|connection[-_]?string)(?:[=\\s,][^\\s]*)?/gi,\n // Short flags: -t value, -p value. Only the SEPARATED form (`-t value`,\n // `-t=value`) is matched \u2014 the glued form (`-tvalue`) is intentionally\n // NOT matched because it produces too many false positives in practice\n // (`-target`, `-tries`, `-timeout` all start with `-t`). A user typing\n // `curl -tSECRET` is extremely rare; a user typing `clang -target=...`\n // is daily. The lookbehind `(?<![-\\w])` rejects `-t` inside `--token`\n // where the preceding char is another `-`.\n /(?<![-\\w])-t(?:[\\s=][^\\s,]+)/,\n /(?<![-\\w])-(?:p|password)(?:[\\s=][^\\s,]+)/gi,\n // env-var style: TOKEN=x, API_KEY=y, DATABASE_URL=z, \u2026\n /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD|DATABASE_URL|CONNECTION_STRING)\\s*[=:][^\\s,]+/gi,\n // Generic high-entropy look \u2014 only when preceded by a flag name.\n /--\\w*(?:token|key|secret|password|passwd|auth|credential)\\w*[=\\s,][A-Za-z0-9+/=]{32,}/,\n];\n\n/**\n * Replace sensitive flag values and env-style secrets with `[REDACTED]`.\n * Pure: never mutates the input. Safe to call on already-redacted text\n * (idempotent \u2014 `[REDACTED]` does not match any pattern).\n */\nexport function redactSecrets(text: string): string {\n let result = text;\n for (const pattern of SENSITIVE_FLAG_PATTERNS) {\n result = result.replace(pattern, (match) => {\n const eq = match.indexOf('=');\n const sp = match.search(/\\s/);\n let delim: string | null = null;\n let delimIdx = -1;\n if (eq !== -1) {\n delim = '=';\n delimIdx = eq;\n } else if (sp !== -1) {\n delim = match[sp]!;\n delimIdx = sp;\n }\n if (delim !== null && delimIdx >= 0) {\n const flag = match.slice(0, delimIdx + 1);\n return `${flag}[REDACTED]`;\n }\n // No clear delimiter (e.g. `-tVALUE` glued to flag name) \u2014 wipe the\n // whole match. We can't tell where the flag name ends and the\n // value begins, so we redact the entire token. Using a single\n // fixed marker (not `flag+marker`) avoids leaking the original\n // value when our char-class-based flag extraction is too greedy\n // (the regex would otherwise match the value characters too).\n return '**redacted**';\n });\n }\n return result;\n}\n", "// ---------------------------------------------------------------------------\n// Humanizers for agent events forwarded to Telegram.\n//\n// The host emits rich structured events; this module turns them into short,\n// readable chat messages. Kept pure (no bot / IO) so it's trivially testable.\n//\n// Design rules for Telegram readability:\n// - Start with an emoji status icon so the outcome is scannable.\n// - Lead with the *headline* (what happened), then context, then stats.\n// - Never embed raw JSON. Never concatenate object dumps.\n// - Keep messages under 2000 chars so they fit one mobile screen.\n// - Use emoji sparingly \u2014 status markers only, no decoration.\n// - Run output through `redactSecrets` before formatting \u2014 a chat\n// notification is the highest-risk exfiltration surface for any token\n// that happens to land in tool output (see packages/telegram/src/redact.ts).\n// ---------------------------------------------------------------------------\n\nimport { redactSecrets } from './redact.js';\n\n// ---------------------------------------------------------------------------\n// Payload types (subsets of core event shapes)\n// ---------------------------------------------------------------------------\n\n/** Subset of the core `delegate.completed` event payload we render. */\nexport interface DelegateCompletedLike {\n target: string;\n task: string;\n ok: boolean;\n status?: string | undefined;\n summary: string;\n durationMs: number;\n iterations: number;\n toolCalls: number;\n costUsd?: number | undefined;\n subagentId?: string | undefined;\n}\n\n/** Subset of core `tool.executed` event payload. */\nexport interface ToolExecutedLike {\n name: string;\n ok: boolean;\n durationMs: number;\n /** Raw tool output \u2014 only the first 300 chars are rendered. */\n output?: string | undefined;\n}\n\n/** Subset of core `session.ended` event payload (from Usage). */\nexport interface SessionEndedLike {\n id: string;\n inputTokens: number;\n outputTokens: number;\n cacheRead?: number | undefined;\n cacheWrite?: number | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Compact human duration: `42s`, `3m`, `1.5h`. */\nexport function fmtDuration(ms: number): string {\n if (ms < 60_000) return `${Math.round(ms / 1000)}s`;\n if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`;\n return `${(ms / 3_600_000).toFixed(1)}h`;\n}\n\n/**\n * Format a numeric count of tokens for human readability.\n * Uses comma-separated thousands: 1,234, 56,789.\n */\nexport function fmtTokens(n: number): string {\n return n.toLocaleString('en-US');\n}\n\n/**\n * Try to render a tool's output as a short human-readable snippet.\n * Strips JSON braces/quoting, redacts secrets, limits to ~300 chars,\n * preserves first/last lines.\n */\nexport function fmtToolOutput(raw: string | undefined): string {\n if (!raw) return '(no output)';\n // Redact BEFORE the JSON-stripping pass so we don't transform the\n // redacted marker (e.g. `[REDACTED]` survives untouched).\n const redacted = redactSecrets(raw);\n const cleaned = redacted\n .replace(/^[{[]\\s*/, '') // strip leading JSON opening\n .replace(/\\s*[}\\]]$/, '') // strip trailing JSON closing\n .replace(/\"([^\"]+)\":/g, '$1: ') // unquote JSON keys, add space for readability\n .replace(/\\\\n/g, '\\n') // expand escaped newlines\n .replace(/\\\\\"/g, '\"') // expand escaped quotes\n .trim()\n || redacted;\n\n // Try to split into short lines; show the first 3 meaningful ones.\n const lines = cleaned.split('\\n').filter((l) => l.trim().length > 0);\n let preview = lines.slice(0, 3).join('\\n');\n if (lines.length > 3) preview += `\\n\u2026 +${lines.length - 3} more lines`;\n if (preview.length > 300) preview = `${preview.slice(0, 297)}\u2026`;\n return preview;\n}\n\n// ---------------------------------------------------------------------------\n// Event \u2192 message formatters\n// ---------------------------------------------------------------------------\n\n/**\n * Render a finished delegation as a readable Telegram message.\n *\n * Example:\n * \u2705 Delegate \u2192 bug-hunter \u00B7 success\n * Found 3 null-deref risks in auth.ts and patched the worst one\u2026\n * \u23F1 3m \u00B7 4 iter \u00B7 37 tools \u00B7 \uD83D\uDCB20.0820\n */\nexport function formatDelegateCompleted(e: DelegateCompletedLike): string {\n const icon = e.ok ? '\u2705' : '\u274C';\n const status = e.status ?? (e.ok ? 'success' : 'failed');\n const task = e.task.length > 160 ? `${e.task.slice(0, 159)}\u2026` : e.task;\n\n // Prefer the host's one-line summary; fall back to echoing the task when a\n // failure produced no summary. Both go through `redactSecrets` \u2014 a\n // delegate summary can contain raw tool output that itself leaks tokens.\n const rawBody = e.summary?.trim() || `(no summary) \u2014 ${task}`;\n const body = redactSecrets(rawBody);\n\n const stats = [\n `\u23F1 ${fmtDuration(e.durationMs)}`,\n `${e.iterations} iter`,\n `${e.toolCalls} tools`,\n ];\n if (typeof e.costUsd === 'number' && e.costUsd > 0) {\n stats.push(`\uD83D\uDCB2${e.costUsd.toFixed(4)}`);\n }\n\n return [`${icon} Delegate \u2192 ${e.target} \u00B7 ${status}`, body, stats.join(' \u00B7 ')].join('\\n');\n}\n\n/**\n * Render a long-running tool execution notification.\n *\n * Example:\n * \u2705 bash completed in 45.2s\n * pnpm test \u2014 12 suites, 47 tests passed\n * \u2026\n */\nexport function formatToolExecuted(e: ToolExecutedLike): string {\n const icon = e.ok ? '\u2705' : '\u274C';\n const sec = (e.durationMs / 1000).toFixed(1);\n const headline = `${icon} ${e.name} completed in ${sec}s`;\n\n const output = fmtToolOutput(e.output);\n // Only include output if it's short enough to be readable on mobile\n if (output === '(no output)') return headline;\n return `${headline}\\n${output}`;\n}\n\n/**\n * Render a session-end notification.\n *\n * Example:\n * \uD83C\uDFC1 Session sess_abcd ended\n * \u2B07 8,234 in \u00B7 \u2B06 3,456 out \u00B7 11,690 total\n * Cache: 1,200 read \u00B7 800 written\n */\nexport function formatSessionEnded(e: SessionEndedLike): string {\n const id = e.id.length > 8 ? e.id.slice(0, 8) : e.id;\n const total = e.inputTokens + e.outputTokens;\n\n const lines = [\n `\uD83C\uDFC1 Session ${id} ended`,\n `\u2B07 ${fmtTokens(e.inputTokens)} in \u00B7 \u2B06 ${fmtTokens(e.outputTokens)} out \u00B7 ${fmtTokens(total)} total`,\n ];\n\n // Show cache stats when available\n if (e.cacheRead || e.cacheWrite) {\n const parts: string[] = [];\n if (e.cacheRead && e.cacheRead > 0) parts.push(`${fmtTokens(e.cacheRead)} cache read`);\n if (e.cacheWrite && e.cacheWrite > 0) parts.push(`${fmtTokens(e.cacheWrite)} cache written`);\n if (parts.length > 0) lines.push(`\uD83D\uDCE6 ${parts.join(' \u00B7 ')}`);\n }\n\n return lines.join('\\n');\n}\n", "import { createHash, randomUUID } from 'node:crypto';\nimport { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport type { Logger } from '@wrongstack/core/types';\nimport { wstackGlobalRoot } from '@wrongstack/core/utils';\n\n/**\n * Cross-process single-poller lock for a Telegram bot token.\n *\n * Telegram allows exactly one `getUpdates` consumer per token; two wstack\n * instances (TUI + WebUI, or two projects) polling the same token fight each\n * other and every cycle returns HTTP 409. This lock elects one poller: the\n * holder writes a heartbeat to a lock file under `~/.wrongstack/telegram/`,\n * other instances stand by and take over when the heartbeat goes stale or\n * the file disappears.\n */\n\ninterface LockFilePayload {\n /** Unique per PollLock instance \u2014 `pid` alone can't distinguish two locks in one process. */\n id: string;\n pid: number;\n acquiredAt: number;\n heartbeatAt: number;\n}\n\nexport interface PollLockOptions {\n log?: Logger | undefined;\n /** How often the holder refreshes its heartbeat. Default: 15s. */\n heartbeatMs?: number | undefined;\n /** A lock whose heartbeat is older than this is considered stale. Default: 45s. */\n staleMs?: number | undefined;\n}\n\n/** Lock file path for a bot token. The token itself never appears in the path. */\nexport function lockPathForToken(token: string, globalRoot = wstackGlobalRoot()): string {\n const hash = createHash('sha256').update(token).digest('hex').slice(0, 12);\n return join(globalRoot, 'telegram', `poll-${hash}.lock`);\n}\n\nexport class PollLock {\n private readonly id = `${process.pid}:${randomUUID()}`;\n private readonly heartbeatMs: number;\n private readonly staleMs: number;\n private readonly log?: Logger | undefined;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private _held = false;\n\n /** Invoked when the lock is stolen by another instance while held. */\n onLost?: (() => void) | undefined;\n\n constructor(\n readonly lockPath: string,\n opts?: PollLockOptions,\n ) {\n this.heartbeatMs = opts?.heartbeatMs ?? 15_000;\n this.staleMs = opts?.staleMs ?? 45_000;\n this.log = opts?.log;\n }\n\n get held(): boolean {\n return this._held;\n }\n\n /**\n * Try to acquire the lock. Returns true when this instance is now (or was\n * already) the holder. Safe to call repeatedly from a standby retry loop.\n */\n tryAcquire(): boolean {\n if (this._held) return true;\n\n const existing = this.readLock();\n if (existing && !this.isStale(existing)) return false;\n\n try {\n mkdirSync(dirname(this.lockPath), { recursive: true });\n // Remove any stale or corrupt file first, then create exclusively: when\n // two standby instances race for a stale lock, `wx` makes exactly one win.\n try {\n unlinkSync(this.lockPath);\n } catch {\n // Nothing to remove, or a competing instance already removed it.\n }\n const now = Date.now();\n const payload: LockFilePayload = {\n id: this.id,\n pid: process.pid,\n acquiredAt: now,\n heartbeatAt: now,\n };\n writeFileSync(this.lockPath, JSON.stringify(payload), { flag: 'wx' });\n } catch {\n return false; // Lost the race or the directory is unwritable.\n }\n\n this._held = true;\n this.startHeartbeat();\n return true;\n }\n\n /** Release the lock and stop the heartbeat. Idempotent. */\n release(): void {\n this.stopHeartbeat();\n if (!this._held) return;\n this._held = false;\n try {\n if (this.readLock()?.id === this.id) unlinkSync(this.lockPath);\n } catch {\n // Best effort \u2014 a stale file is reclaimed via the staleness check anyway.\n }\n }\n\n // ------------------------------------------------------------------\n // Internals\n // ------------------------------------------------------------------\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => this.heartbeatTick(), this.heartbeatMs);\n this.heartbeatTimer.unref?.();\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private heartbeatTick(): void {\n const current = this.readLock();\n if (!current || current.id !== this.id) {\n // Another instance stole the lock (e.g. this process was suspended past\n // the staleness window). Stop claiming it and notify the owner.\n this._held = false;\n this.stopHeartbeat();\n this.log?.warn('Telegram: poll lock was taken over by another instance.');\n this.onLost?.();\n return;\n }\n try {\n const payload: LockFilePayload = { ...current, heartbeatAt: Date.now() };\n // Write via temp + rename so a reader never sees a half-written file.\n const tmp = `${this.lockPath}.${process.pid}.tmp`;\n writeFileSync(tmp, JSON.stringify(payload));\n renameSync(tmp, this.lockPath);\n } catch (err) {\n this.log?.debug(`Telegram: poll lock heartbeat write failed: ${err}`);\n }\n }\n\n private readLock(): LockFilePayload | null {\n try {\n const raw = readFileSync(this.lockPath, 'utf8');\n const parsed = JSON.parse(raw) as LockFilePayload;\n if (typeof parsed.id !== 'string' || typeof parsed.pid !== 'number') return null;\n // A non-finite heartbeatAt makes `Date.now() - heartbeatAt` NaN, and\n // `NaN > staleMs` is false, so the staleness check would silently never\n // fire and the lock could wedge every standby instance forever. Reject\n // it as corrupt so the file is reclaimed.\n if (!Number.isFinite(parsed.heartbeatAt)) return null;\n return parsed;\n } catch {\n return null; // Missing or corrupt \u2014 treated as stale/absent.\n }\n }\n\n private isStale(payload: LockFilePayload): boolean {\n if (Date.now() - payload.heartbeatAt > this.staleMs) return true;\n return !this.isPidAlive(payload.pid);\n }\n\n private isPidAlive(pid: number): boolean {\n if (pid === process.pid) return true;\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM means the process exists but belongs to another user.\n return (err as NodeJS.ErrnoException).code === 'EPERM';\n }\n }\n}\n", "import { createHash } from 'node:crypto';\nimport {\n closeSync,\n fsyncSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { wstackGlobalRoot } from '@wrongstack/core/utils';\n\n/**\n * Offset file path for a bot token. The token itself never appears in the path.\n * Uses the same hash convention as PollLock so both are discoverable together.\n */\nexport function offsetPathForToken(token: string, globalRoot = wstackGlobalRoot()): string {\n const hash = createHash('sha256').update(token).digest('hex').slice(0, 12);\n return join(globalRoot, 'telegram', `offset-${hash}.json`);\n}\n\n/**\n * Typed offset-cursor persistence for Telegram bot polling.\n *\n * Writes are atomic (temp file + rename) so a crash mid-write never leaves a\n * corrupt or incomplete file. Reads handle missing, empty, and malformed files\n * transparently \u2014 the caller always gets a valid non-negative number or null.\n */\nexport interface OffsetStoreOptions {\n /** Bot token \u2014 derives a token-scoped default path. Never persisted. */\n token?: string | undefined;\n /**\n * Explicit file path override. Takes precedence over token derivation. An\n * empty string is treated as \"no path\", which disables persistence.\n */\n path?: string | undefined;\n /** Base directory for token-scoped derivation (defaults to wstackGlobalRoot). */\n globalRoot?: string | undefined;\n}\n\nexport class OffsetStore {\n private readonly path: string;\n\n constructor(opts: OffsetStoreOptions = {}) {\n if (opts.path !== undefined) {\n this.path = opts.path;\n } else if (opts.token) {\n this.path = offsetPathForToken(opts.token, opts.globalRoot);\n } else {\n this.path = '';\n }\n }\n\n /** The derived path for diagnostics. */\n get storePath(): string {\n return this.path;\n }\n\n /**\n * Read the persisted offset. Returns null when the file is missing, empty,\n * or contains a value that is not a valid non-negative integer.\n */\n read(): number | null {\n if (!this.path) return null;\n\n let raw: string;\n try {\n raw = readFileSync(this.path, 'utf8').trim();\n } catch {\n return null;\n }\n\n if (raw.length === 0) return null;\n\n try {\n const parsed = JSON.parse(raw);\n if (\n typeof parsed !== 'number' ||\n !Number.isFinite(parsed) ||\n parsed < 0 ||\n parsed % 1 !== 0\n ) {\n return null;\n }\n return parsed;\n } catch {\n return null;\n }\n }\n\n /**\n * Persist an offset value using an atomic write (temp file + rename).\n * Creates the parent directory on first call.\n */\n write(offset: number): void {\n if (!this.path || offset < 0) return;\n\n mkdirSync(dirname(this.path), { recursive: true });\n\n const tmp = `${this.path}.${process.pid}.tmp`;\n // Write to a temp file, fsync it to durable storage, then atomically rename\n // over the target. fsync before rename guarantees the bytes are on disk on\n // both POSIX and Windows before the rename makes them visible.\n const fd = openSync(tmp, 'w');\n try {\n writeSync(fd, JSON.stringify(offset));\n fsyncSync(fd);\n } finally {\n closeSync(fd);\n }\n try {\n renameSync(tmp, this.path);\n } catch {\n // If rename fails (e.g. cross-device on some setups), clean up the temp\n // file so we don't leak it. The caller can retry on the next poll cycle.\n try {\n unlinkSync(tmp);\n } catch {\n // Temp file removal is best-effort.\n }\n }\n }\n}\n", "import { DefaultSecretScrubber } from '@wrongstack/core/security';\nimport { ToolValidationError } from '@wrongstack/core/types';\nimport { redactSecrets } from '../redact.js';\n\nexport type TelegramChatId = string | number;\n\n/**\n * Narrow capability for creating a Telegram approval request. Unlike the\n * generic `net.outbound` capability, this must be granted explicitly to a\n * subagent before its auto-permission approval tool becomes available.\n */\nexport const TELEGRAM_APPROVAL_CAPABILITY = 'net.outbound.telegram.approval' as const;\n\nexport interface TelegramOutboundTargetPolicy {\n /** Paired/default chat. It is always an allowed outbound target when set. */\n getDefaultChatId(): TelegramChatId | undefined;\n /** Additional explicitly trusted outbound targets, resolved at call time. */\n getAllowedOutboundChatIds?(): readonly TelegramChatId[];\n}\n\nconst secretScrubber = new DefaultSecretScrubber();\nconst RAW_TELEGRAM_BOT_TOKEN = /(?<![A-Za-z0-9])\\d{5,15}:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g;\n\nfunction normalizeChatId(value: TelegramChatId): string {\n return String(value).trim();\n}\n\n/**\n * Resolve an outbound target against the paired chat plus the explicit\n * outbound allowlist. This must run before any Telegram API call.\n */\nexport function resolveTelegramOutboundTarget(\n requestedChatId: TelegramChatId | undefined,\n policy: TelegramOutboundTargetPolicy,\n): TelegramChatId {\n const defaultChatId = policy.getDefaultChatId();\n const target = requestedChatId ?? defaultChatId;\n if (target === undefined || normalizeChatId(target) === '') {\n throw new ToolValidationError({\n field: 'chat_id',\n message:\n 'No chat_id provided and no allowed Telegram target is configured. Pair notifyChatId or configure allowedOutboundChats.',\n });\n }\n\n const allowed = new Set<string>();\n if (defaultChatId !== undefined && normalizeChatId(defaultChatId) !== '') {\n allowed.add(normalizeChatId(defaultChatId));\n }\n for (const chatId of policy.getAllowedOutboundChatIds?.() ?? []) {\n const normalized = normalizeChatId(chatId);\n if (normalized !== '') allowed.add(normalized);\n }\n\n if (!allowed.has(normalizeChatId(target))) {\n throw new ToolValidationError({\n field: 'chat_id',\n message:\n 'Telegram outbound target is not paired or included in allowedOutboundChats.',\n });\n }\n\n return typeof target === 'string' ? target.trim() : target;\n}\n\n/**\n * Scrub outbound text with the shared core credential detector, then retain\n * Telegram's legacy flag/env redaction for labelled secrets the core patterns\n * intentionally do not classify by value alone.\n */\nexport function scrubTelegramOutboundText(text: string): string {\n const shared = secretScrubber.scrub(text);\n const withoutBareBotTokens = shared.replace(\n RAW_TELEGRAM_BOT_TOKEN,\n '[REDACTED:telegram_bot_token]',\n );\n return redactSecrets(withoutBareBotTokens);\n}\n", "import type { PluginAPI } from '@wrongstack/core/plugin';\nimport type { SlashCommand } from '@wrongstack/core/types';\nimport { expectDefined } from '@wrongstack/core/utils';\nimport { type TelegramBot, truncateForTelegram } from '../bot.js';\nimport type { TelegramPluginConfig } from '../config.js';\nimport {\n resolveTelegramOutboundTarget,\n scrubTelegramOutboundText,\n type TelegramOutboundTargetPolicy,\n} from '../security/outbound.js';\nimport type { TelegramBotOutbound } from '../bot-queue.js';\n\n// ---------------------------------------------------------------------------\n// /telegram-health\n// ---------------------------------------------------------------------------\n\nexport function tgHealthCommand(bot: TelegramBot, cfg: TelegramPluginConfig): SlashCommand {\n return {\n name: 'telegram-health',\n aliases: ['telegram', 'tgstat', 'tgs'],\n description: 'Show Telegram bot connection health and config',\n help: `Usage: /telegram-health\nAliases: /telegram, /tgstat, /tgs\n\nShows whether the bot is connected, its username, polling interval,\nallowlist health, and notification settings.`,\n async run(_args, _ctx) {\n const health = await bot.health();\n const lines = [\n '\u2550\u2550\u2550 Telegram Plugin Status \u2550\u2550\u2550',\n '',\n `Bot: ${health.ok ? `\u2705 @${health.username ?? 'connected'}` : `\u274C ${health.error ?? 'offline'}`}`,\n `Running: ${bot.running ? 'yes' : 'no'}`,\n `Started: ${bot.startedAt ? new Date(bot.startedAt).toLocaleTimeString() : 'N/A'}`,\n `Poll: every ${cfg.pollIntervalSec ?? 2}s`,\n `Allowed: ${(cfg.allowedUsers?.length ?? 0) > 0 ? `${cfg.allowedUsers?.length} users` : 'everyone (users)'} / ${(cfg.allowedChats?.length ?? 0) > 0 ? `${cfg.allowedChats?.length} chats` : 'everyone (chats)'}`,\n `Notify: sessionEnd=${cfg.notifyOnSessionEnd ?? false}, longTool=${cfg.longToolThresholdMs ? `${cfg.longToolThresholdMs}ms` : 'off'}`,\n ];\n\n return { message: lines.join('\\n') };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// /telegram:send\n// ---------------------------------------------------------------------------\n\ninterface TelegramSlashSendPolicy extends TelegramOutboundTargetPolicy {\n getMaxMessageLength?(): number;\n}\n\nexport function tgSendCommand(\n bot: TelegramBot,\n policyOrDefault: TelegramSlashSendPolicy | string | number | undefined,\n outbound?: TelegramBotOutbound,\n): SlashCommand {\n const policy: TelegramSlashSendPolicy =\n typeof policyOrDefault === 'object' && policyOrDefault !== null\n ? policyOrDefault\n : { getDefaultChatId: () => policyOrDefault };\n\n return {\n name: 'send',\n description: 'Send a message to a Telegram chat',\n help: `Usage: /telegram:send [chat_id] <message>\n\nSend a message to a Telegram chat.\n- First argument (optional): chat or user ID. Uses notifyChatId from config when omitted.\n- Everything else: the message text.\n\nExamples:\n /telegram:send 123456789 Build completed successfully \u2713\n /telegram:send Deploy finished \u2014 check staging`,\n async run(args, _ctx) {\n if (!args.trim()) {\n return { message: 'Usage: /telegram:send [chat_id] <message>' };\n }\n\n let requestedChatId: string | number | undefined;\n let text: string;\n\n // First token might be a numeric chat_id. Telegram group/supergroup IDs\n // are negative, so accept an optional leading minus sign.\n const parts = args.trim().split(/\\s+/);\n const maybeId = parts[0];\n if (/^-?\\d+$/.test(expectDefined(maybeId)) && parts.length > 1) {\n requestedChatId = expectDefined(maybeId);\n text = parts.slice(1).join(' ');\n } else {\n text = args.trim();\n }\n\n try {\n const chatId = resolveTelegramOutboundTarget(requestedChatId, policy);\n const scrubbed = scrubTelegramOutboundText(text);\n const truncated = truncateForTelegram(scrubbed, policy.getMaxMessageLength?.() ?? 4000);\n if (outbound) {\n const res = await outbound.sendManual(chatId, truncated);\n return {\n message: `\u2705 Message sent to ${chatId} (msg_id=${res.result?.message_id ?? '?'})`,\n };\n }\n const res = await bot.sendMessage(chatId, truncated);\n return {\n message: `\u2705 Message sent to ${chatId} (msg_id=${res.result?.message_id ?? '?'})`,\n };\n } catch (err) {\n return { message: `\u274C Failed to send: ${(err as Error).message}` };\n }\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// /telegram:chatid\n// ---------------------------------------------------------------------------\n\nexport function tgChatIdCommand(defaultChatId?: string | number): SlashCommand {\n const chatIdStr = defaultChatId ? String(defaultChatId) : null;\n return {\n name: 'chatid',\n description: 'Show the configured default chat ID',\n help: `Usage: /telegram:chatid\n\nShows the current default notifyChatId used for notifications\nand the \\`telegram_send\\` tool when no chat_id is specified.`,\n async run(_args, _ctx) {\n if (chatIdStr) {\n return { message: `Configured notifyChatId: ${chatIdStr}` };\n }\n return {\n message:\n 'No notifyChatId configured. Set it in the plugin config or pass chat_id explicitly to telegram_send.',\n };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Register all\n// ---------------------------------------------------------------------------\n\nexport function registerSlashCommands(\n api: PluginAPI,\n bot: TelegramBot,\n cfg: TelegramPluginConfig,\n): string[] {\n const cmds = [\n tgHealthCommand(bot, cfg),\n tgSendCommand(bot, {\n getDefaultChatId: () => cfg.notifyChatId,\n getAllowedOutboundChatIds: () => cfg.allowedOutboundChats ?? [],\n getMaxMessageLength: () => cfg.maxMessageLength ?? 4000,\n }),\n tgChatIdCommand(cfg.notifyChatId),\n ];\n for (const cmd of cmds) api.slashCommands.register(cmd);\n return cmds.map((c) => c.name);\n}\n", "import { randomUUID } from 'node:crypto';\nimport type { Logger, Tool } from '@wrongstack/core/types';\nimport type { TelegramBot } from '../bot.js';\nimport { truncateForTelegram } from '../bot.js';\nimport {\n resolveTelegramOutboundTarget,\n scrubTelegramOutboundText,\n TELEGRAM_APPROVAL_CAPABILITY,\n type TelegramChatId,\n} from '../security/outbound.js';\n\ninterface TelegramApproveInput {\n /** Short label for what's being approved (\u2264 60 chars). Shown as the prompt heading. */\n prompt: string;\n /** Optional details (\u2264 1000 chars). Shown under the heading. */\n details?: string | undefined;\n /** Chat to post the prompt to. Falls back to notifyChatId. */\n chat_id?: string | number | undefined;\n /** How long to wait for a button press before auto-denying. Default 60s, max 600s. */\n timeout_ms?: number | undefined;\n}\n\ninterface TelegramApproveOutput {\n approved: boolean;\n /** Immutable Telegram user ID; absent for timeout, shutdown, or rejection. */\n user_id?: number | undefined;\n /** Human-readable username/first name; never used for authorization. */\n display_name: string;\n /** Backward-compatible alias for display_name. */\n from: string;\n prompt_message_id?: number | undefined;\n}\n\n/**\n * Post a yes/no inline-keyboard prompt to a chat and block until the user\n * taps a button (or until `timeout_ms` elapses, in which case the call\n * auto-denies). Useful when the agent wants explicit approval before\n * continuing and the user is on their phone rather than the TUI.\n *\n * The agent calls this tool directly. It does not replace the host-level\n * `permission: 'confirm'` flow \u2014 for that, see the future B4 work.\n *\n * Permission: `auto` (NOT `confirm`). This is intentional \u2014 the tool's\n * purpose IS to obtain user approval; gating it behind another host-level\n * confirm dialog would be circular and would block the agent in\n * headless mode. The user-side approval (Telegram button press) is\n * the only confirm gate. The 600 s tool `timeoutMs` ceiling is the\n * safety net for the case where the user never responds.\n */\nexport function makeTelegramApproveTool(opts: {\n bot: TelegramBot;\n /** Paired/default target, resolved on every call for live config updates. */\n getDefaultChatId(): TelegramChatId | undefined;\n /** Additional trusted targets, resolved on every call for live config updates. */\n getAllowedOutboundChatIds?(): readonly TelegramChatId[];\n /** Immutable Telegram user IDs permitted to resolve an approval. */\n getAllowedUserIds?(): readonly TelegramChatId[];\n /**\n * Group approvals stay denied unless both this and explicit user IDs are\n * configured. Resolved on every call for live config updates.\n */\n getAllowGroupApprovals?(): boolean;\n maxMessageLength: number;\n log: Logger;\n}): Tool<TelegramApproveInput, TelegramApproveOutput> {\n return {\n name: 'telegram_approve',\n description:\n 'Post a scrubbed yes/no prompt only to the paired Telegram chat or an explicitly allowed outbound chat, then wait for a button press. Returns approval state plus immutable user_id and display_name; false means timeout, rejection, or explicit deny. This narrow capability requests remote approval but does not itself authorize or perform the proposed operation.',\n usageHint:\n 'telegram_approve(prompt: \"Delete build artifacts?\", details: \"Frees 2.3 GB. Cannot be undone.\", timeout_ms: 60000)',\n category: 'Telegram',\n inputSchema: {\n type: 'object',\n properties: {\n prompt: {\n type: 'string',\n maxLength: 200,\n description: 'Short label for what is being approved. Shown as the prompt heading.',\n },\n details: {\n type: 'string',\n maxLength: 1000,\n description: 'Optional context under the heading.',\n },\n chat_id: {\n oneOf: [{ type: 'string' }, { type: 'integer' }],\n description: 'Chat to post the prompt to. Uses the plugin default when omitted.',\n },\n timeout_ms: {\n type: 'integer',\n minimum: 1000,\n maximum: 600_000,\n description:\n 'How long to wait before auto-denying. Default 60 000 ms, max 600 000 ms (10 min).',\n },\n },\n required: ['prompt'],\n },\n permission: 'auto',\n mutating: true,\n riskTier: 'standard',\n capabilities: [TELEGRAM_APPROVAL_CAPABILITY],\n timeoutMs: 610_000,\n async execute(input, ctx, toolOpts) {\n const chatId = resolveTelegramOutboundTarget(input.chat_id, opts);\n const timeoutMs = Math.min(Math.max(input.timeout_ms ?? 60_000, 1000), 600_000);\n const configuredUserIds = opts.getAllowedUserIds?.().map(String) ?? [];\n const isGroup = String(chatId).startsWith('-');\n if (isGroup && (opts.getAllowGroupApprovals?.() !== true || configuredUserIds.length === 0)) {\n throw new Error('Telegram group approvals require explicit per-user configuration.');\n }\n const expectedUserIds = configuredUserIds.length > 0 ? configuredUserIds : [String(chatId)];\n\n // Stable request identity shared by the yes/no callback actions.\n const requestId = randomUUID().slice(0, 16);\n const yesKey = `approve:${requestId}:yes`;\n const noKey = `approve:${requestId}:no`;\n\n // Scrub every user-controlled outbound field before truncation so raw\n // credentials never reach Telegram, even inside an approval prompt.\n const prompt = scrubTelegramOutboundText(input.prompt);\n const details = input.details\n ? truncateForTelegram(scrubTelegramOutboundText(input.details), 800)\n : undefined;\n const heading = `\u26A0\uFE0F ${prompt}`;\n const detailsLine = details ? `\\n\\n${details}` : '';\n const text = `${heading}${detailsLine}\\n\\n_Reply by tapping a button. Auto-denies in ${Math.round(timeoutMs / 1000)}s._`;\n\n opts.log.info(`telegram_approve \u2192 chat_id=${chatId} (${prompt.length} prompt chars)`);\n\n // Register before sending so an immediate callback cannot beat waiter\n // creation. The same request owns its timer through send, bind, and\n // terminal settlement.\n const approval = opts.bot.awaitApproval({\n requestId,\n sessionId: ctx?.session.id ?? 'unknown-session',\n expectedChatId: chatId,\n expectedUserIds,\n allowGroup: isGroup && opts.getAllowGroupApprovals?.() === true,\n expiresAt: Date.now() + timeoutMs,\n signal: toolOpts?.signal,\n });\n\n let promptMessageId: number | undefined;\n try {\n const sent = await opts.bot.sendMessageWithKeyboard(chatId, text, [\n { text: '\u2705 Approve', callback_data: yesKey },\n { text: '\u274C Deny', callback_data: noKey },\n ], toolOpts?.signal);\n promptMessageId = sent.result?.message_id;\n if (promptMessageId === undefined) {\n throw new Error('Telegram approval prompt response did not include a message ID.');\n }\n if (!opts.bot.bindApprovalPrompt(requestId, promptMessageId)) {\n throw new Error('Telegram approval request ended before its prompt could be bound.');\n }\n } catch (err) {\n opts.bot.cancelApproval(requestId, 'send-failed');\n await approval;\n opts.log.debug(`telegram_approve send failed: ${(err as Error).message}`);\n throw err;\n }\n\n const result = await approval;\n return {\n approved: result.approved,\n user_id: result.fromUserId,\n display_name: result.fromUser,\n from: result.fromUser,\n prompt_message_id: promptMessageId,\n };\n },\n };\n}\n", "// ---------------------------------------------------------------------------\n// Bounded outbound send queue with per-chat backpressure.\n//\n// Replaces ad-hoc fire-and-forget Promise chains at the notification\n// call sites (session.ended, tool.executed, delegate.completed) so a flood\n// of events cannot create unbounded in-flight promises, and so one slow\n// chat cannot stall messages destined for other chats.\n//\n// Contract:\n// - Manual sends (telegram_send tool, /telegram:send) are user-triggered;\n// enqueue() either sends synchronously or rejects with a clear error.\n// They are never silently dropped or coalesced.\n// - Automatic notifications are best-effort: when a chat's queue is full,\n// the oldest pending entry is dropped (counted in stats) and a debug\n// log records the drop. The newest entry is enqueued.\n// - Per-chat ordering: messages to the same chatId are serialised in the\n// order enqueue() was called for them.\n// - Cross-chat concurrency: independent chats are dispatched in parallel.\n// - Backpressure: when a single chat exceeds `maxPerChat`, the oldest\n// pending notification entry is dropped (logged + counted) to keep the\n// queue bounded. Manual entries instead reject with an overflow error\n// so the caller never gets a silent failure.\n// - Drain on stop(): pending entries are flushed best-effort; subsequent\n// enqueues after stop() reject with a clear error.\n// ---------------------------------------------------------------------------\n\nimport type { Logger } from '@wrongstack/core/types';\n\nexport type OutboundKind = 'notification' | 'manual';\n\nexport interface OutboundEntry {\n readonly chatId: string | number;\n readonly text: string;\n /** Manual = user-triggered (never dropped). Notification = best-effort. */\n readonly kind: OutboundKind;\n}\n\n/**\n * Internal entry shape. The `id` is assigned **once** at enqueue time and\n * reused for every resolver lookup (run/stop). Crucially, it must NOT be\n * recomputed from a mutable counter at completion time \u2014 if it were, two\n * interleaved sends to the same chat would diverge in their keys and one\n * would hang forever (the resolver would never be found).\n */\ninterface InternalEntry extends OutboundEntry {\n readonly id: number;\n}\n\nexport interface OutboundQueueOptions {\n /** Bound per chat; default 32. Older pending entries are dropped on overflow. */\n readonly maxPerChat?: number | undefined;\n /** Bound for the total concurrent API calls; default 4. */\n readonly maxConcurrency?: number | undefined;\n /**\n * Producer of the actual HTTP call. The queue invokes this exactly once\n * per dequeued entry and propagates the resolved value (or rejection) to\n * the original enqueue caller. Keeping this as an injected function lets\n * the queue own ordering/backpressure while tests swap a fake transport.\n */\n readonly send: (chatId: string | number, text: string) => Promise<unknown>;\n readonly log?: Logger | undefined;\n}\n\nexport interface OutboundQueueStats {\n readonly enqueued: number;\n readonly sent: number;\n readonly dropped: number;\n readonly failed: number;\n readonly inflight: number;\n /** Accepted entries not yet settled, including the in-flight sends. */\n readonly pending: number;\n}\n\n/** Per-chat serial queue state. */\ninterface ChatLane {\n pending: InternalEntry[];\n running: boolean;\n}\n\nconst DEFAULT_MAX_PER_CHAT = 32;\nconst DEFAULT_MAX_CONCURRENCY = 4;\n\nexport class OutboundQueue {\n readonly #opts: {\n maxPerChat: number;\n maxConcurrency: number;\n send: (chatId: string | number, text: string) => Promise<unknown>;\n log: Logger | undefined;\n };\n readonly #lanes = new Map<string, ChatLane>();\n #active = 0;\n #notificationScheduleQueued = false;\n #stopped = false;\n #nextId = 0;\n #enqueued = 0;\n #sent = 0;\n #dropped = 0;\n #failed = 0;\n #resolvers = new Map<\n number,\n { resolve: (value: unknown) => void; reject: (err: unknown) => void }\n >();\n\n constructor(opts: OutboundQueueOptions) {\n const maxPerChat = Math.max(1, opts.maxPerChat ?? DEFAULT_MAX_PER_CHAT);\n const maxConcurrency = Math.max(1, opts.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY);\n this.#opts = {\n maxPerChat,\n maxConcurrency,\n send: opts.send,\n log: opts.log,\n };\n }\n\n /**\n * Enqueue an outbound send. For manual entries the returned promise\n * resolves with the send result (or rejects with the send error / the\n * overflow error). For notification entries the returned promise resolves\n * as soon as the queue accepts the entry, so callers don't block;\n * downstream drain failures are logged and counted but do not propagate.\n */\n enqueue(entry: OutboundEntry): Promise<unknown> {\n if (this.#stopped) {\n return Promise.reject(new Error('Outbound queue is stopped'));\n }\n const internal: InternalEntry = { ...entry, id: this.#mintId() };\n const key = String(entry.chatId);\n let lane = this.#lanes.get(key);\n if (!lane) {\n lane = { pending: [], running: false };\n this.#lanes.set(key, lane);\n }\n if (entry.kind === 'notification') {\n if (lane.pending.length >= this.#opts.maxPerChat) {\n // Only drop notification entries \u2014 never displace a pending manual.\n // Manuals register a completion resolver; dropping one without\n // settling it would hang the caller forever (and leak the resolver).\n const dropIndex = lane.pending.findIndex((e) => e.kind === 'notification');\n if (dropIndex === -1) {\n // All pending entries are manuals; drop the incoming best-effort\n // notification instead of displacing a manual send.\n this.#dropped += 1;\n this.#opts.log?.debug(\n `Telegram outbound queue dropped an incoming notification for chat ${entry.chatId} (per-chat limit ${this.#opts.maxPerChat}; only manual entries pending)`,\n );\n return Promise.resolve(undefined);\n }\n const dropped = lane.pending.splice(dropIndex, 1)[0]!;\n this.#dropped += 1;\n this.#opts.log?.debug(\n `Telegram outbound queue dropped a notification for chat ${dropped.chatId} (per-chat limit ${this.#opts.maxPerChat})`,\n );\n }\n } else if (lane.pending.length + (lane.running ? 1 : 0) >= this.#opts.maxPerChat) {\n // Manual overflow: surface a real error instead of silently dropping.\n // Per the P1.4 acceptance criterion, manual sends are never\n // silently dropped or coalesced.\n // Counts both pending and in-flight entries since `running` means one\n // entry has been dequeued from pending but is still being sent.\n return Promise.reject(\n new Error(\n `Telegram outbound queue per-chat limit reached for chat ${entry.chatId} (max ${this.#opts.maxPerChat})`,\n ),\n );\n }\n lane.pending.push(internal);\n this.#enqueued += 1;\n\n if (entry.kind === 'notification') {\n // Notification delivery is best-effort. Its promise represents queue\n // acceptance, not transport completion, so event handlers never block\n // behind a slow Telegram request. Batch notifications enqueued in the\n // same turn before dispatching; this lets the bounded queue consistently\n // drop the oldest entries during a burst.\n this.#scheduleNotifications();\n return Promise.resolve(undefined);\n }\n\n return new Promise<unknown>((resolve, reject) => {\n this.#resolvers.set(internal.id, { resolve, reject });\n this.#schedule();\n });\n }\n\n /** Stats snapshot for `/telegram-health` and the P3.1 metrics surface. */\n stats(): OutboundQueueStats {\n let pending = this.#active;\n for (const lane of this.#lanes.values()) pending += lane.pending.length;\n return {\n enqueued: this.#enqueued,\n sent: this.#sent,\n dropped: this.#dropped,\n failed: this.#failed,\n inflight: this.#active,\n pending,\n };\n }\n\n /**\n * Stop accepting new entries. Returns a promise that resolves once all\n * currently in-flight sends have settled and every per-chat lane is\n * empty. Pending entries are rejected so their callers don't hang.\n */\n async stop(): Promise<void> {\n this.#stopped = true;\n for (const lane of this.#lanes.values()) {\n for (const entry of lane.pending.splice(0)) {\n this.#dropped += 1;\n const resolver = this.#resolvers.get(entry.id);\n if (resolver) {\n this.#resolvers.delete(entry.id);\n resolver.reject(new Error('Outbound queue stopped before send'));\n }\n this.#opts.log?.debug(\n `Telegram outbound queue stopped, dropped pending ${entry.kind} for chat ${entry.chatId}`,\n );\n }\n }\n while (this.#active > 0) {\n await new Promise((r) => setTimeout(r, 5));\n }\n }\n\n #mintId(): number {\n this.#nextId += 1;\n return this.#nextId;\n }\n\n #scheduleNotifications(): void {\n if (this.#notificationScheduleQueued) return;\n this.#notificationScheduleQueued = true;\n // Two microtask hops preserve immediate acceptance while allowing both\n // sequential awaits and Promise.all acceptance checks to settle before\n // transport work begins. Synchronous notification bursts are therefore\n // bounded as one batch instead of leaking the first entry in-flight.\n queueMicrotask(() => {\n queueMicrotask(() => {\n this.#notificationScheduleQueued = false;\n this.#schedule();\n });\n });\n }\n\n #schedule(): void {\n if (this.#stopped) return;\n // Run up to maxConcurrency inflight across all lanes. Each lane\n // serialises itself so two entries to the same chat never run together.\n while (this.#active < this.#opts.maxConcurrency) {\n const entry = this.#nextReady();\n if (!entry) return;\n this.#active += 1;\n void this.#run(entry);\n }\n }\n\n #nextReady(): InternalEntry | undefined {\n // Prefer lanes whose running=false so each chat progresses in FIFO order\n // even when the global concurrency cap is lower than the lane count.\n for (const lane of this.#lanes.values()) {\n if (!lane.running && lane.pending.length > 0) {\n lane.running = true;\n return lane.pending.shift();\n }\n }\n return undefined;\n }\n\n async #run(entry: InternalEntry): Promise<void> {\n const key = String(entry.chatId);\n // #nextReady marks and returns an entry from this lane; the lane remains\n // registered until this method's finally block prunes it.\n const lane = this.#lanes.get(key)!;\n try {\n const result = await this.#opts.send(entry.chatId, entry.text);\n this.#sent += 1;\n const resolver = this.#resolvers.get(entry.id);\n if (resolver) {\n this.#resolvers.delete(entry.id);\n resolver.resolve(result);\n }\n } catch (err) {\n this.#failed += 1;\n const resolver = this.#resolvers.get(entry.id);\n if (resolver) {\n this.#resolvers.delete(entry.id);\n // Only manual entries retain a completion resolver. Notification\n // promises settle on acceptance, before transport work begins.\n resolver.reject(err);\n } else {\n // Best-effort notification failures are observable through logs and\n // stats without becoming unhandled promise rejections at call sites.\n // Manual entries always have a resolver until their send settles.\n this.#opts.log?.debug(\n `Telegram outbound queue notification failed for chat ${entry.chatId}: ${(err as Error).message}`,\n );\n }\n } finally {\n this.#active -= 1;\n lane.running = false;\n // Prune an idle lane so the per-chat map doesn't grow for the whole\n // process lifetime in unrestricted mode (a bot reachable by many chats).\n // enqueue() lazily recreates it on the next send to this chat.\n if (lane.pending.length === 0) {\n this.#lanes.delete(key);\n }\n this.#schedule();\n }\n }\n}\n", "// ---------------------------------------------------------------------------\n// Per-chat token bucket rate limiter for Telegram outbound sends.\n//\n// Telegram enforces per-chat rate limits:\n// - Groups: ~20 messages per minute (\u2248 0.33 msg/s)\n// - Private chats: ~30 messages per second\n//\n// Instead of hardcoding chat-type limits (which would require an API call\n// per chat to discover the type), this limiter exposes a configurable\n// tokens-per-second and burst cap. The caller (OutboundQueue) decides\n// the policy for each chat based on known chat type.\n//\n// Each chat gets its own token bucket. When a send is attempted and no\n// tokens are available, the caller waits (via waitForToken) until a token\n// refills. The wait is bounded by an optional timeout to prevent head-of-line\n// blocking when a slow chat stalls the queue.\n//\n// Thread safety: the bucket state is guarded by the calling pattern \u2014\n// OutboundQueue serializes per-chat sends (single ChatLane runner), so\n// concurrent access to the same bucket never happens. Cross-chat buckets\n// are independent and need no coordination.\n// ---------------------------------------------------------------------------\n\nexport interface RateLimiterOptions {\n /** Tokens per second (refill rate). Default: 0.33 (\u224820/min for groups). */\n tokensPerSecond?: number | undefined;\n /** Maximum burst size (bucket capacity). Default: 4. */\n burst?: number | undefined;\n}\n\nexport interface TokenBucket {\n /** Wait until a token is available, respecting the optional timeout. */\n waitForToken(timeoutMs?: number | undefined): Promise<void>;\n /** Current fill level (for diagnostics). */\n fill(): number;\n /**\n * True once the bucket has refilled to full capacity. A full, idle bucket\n * holds no pacing state \u2014 recreating it yields an identical full bucket \u2014 so\n * callers can safely evict it to bound a per-chat bucket map without losing\n * any rate-limit state. A depleted (in-use) bucket is never full.\n */\n isFull(): boolean;\n}\n\n/**\n * Create a token bucket for a single chat.\n *\n * The bucket starts full (`burst` tokens). Every `waitForToken` call\n * consumes one token if available; otherwise it waits until a token\n * is refilled. Refills happen at `tokensPerSecond` rate, capped at\n * `burst`.\n *\n * @example\n * ```ts\n * const bucket = createTokenBucket({ tokensPerSecond: 0.33, burst: 4 });\n * await bucket.waitForToken(5_000); // waits up to 5s for a slot\n * bot.sendMessage(chatId, text);\n * ```\n */\nexport function createTokenBucket(opts?: RateLimiterOptions): TokenBucket {\n const tokensPerSecond = opts?.tokensPerSecond ?? 0.33;\n const burst = opts?.burst ?? 4;\n const refillIntervalMs = 1000 / tokensPerSecond;\n\n let tokens = burst;\n let lastRefill = Date.now();\n\n return { waitForToken, fill, isFull };\n\n async function waitForToken(timeoutMs?: number | undefined): Promise<void> {\n const deadline = timeoutMs !== undefined ? Date.now() + timeoutMs : Infinity;\n\n while (true) {\n refill();\n\n if (tokens >= 1) {\n tokens -= 1;\n return;\n }\n\n const now = Date.now();\n if (now >= deadline) {\n return; // Timeout \u2014 caller should proceed or skip\n }\n\n // Wait until the next refill tick or the deadline, whichever is sooner.\n const nextRefill = lastRefill + refillIntervalMs;\n const delay = Math.min(\n Math.max(nextRefill - now, 0),\n deadline - now,\n 5000, // safety cap: never sleep longer than 5s\n );\n\n await sleep(delay);\n }\n }\n\n function refill(): void {\n const now = Date.now();\n const elapsed = now - lastRefill;\n if (elapsed <= 0) return;\n\n const newTokens = (elapsed / 1000) * tokensPerSecond;\n tokens = Math.min(burst, tokens + newTokens);\n lastRefill = now;\n }\n\n function fill(): number {\n refill();\n return tokens;\n }\n\n function isFull(): boolean {\n refill();\n return tokens >= burst;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n", "// ---------------------------------------------------------------------------\n// Telegram outbound queue integration.\n//\n// Provides a TelegramBot helper that routes manual sends (the\n// telegram_send tool, /telegram:send) through the same bounded outbound\n// queue used by automatic notifications, so user-triggered and\n// notification-triggered sends share per-chat ordering and backpressure.\n//\n// Manual entries reject on overflow (caller sees the error), per the P1.4\n// acceptance criterion \"manual sends are never silently dropped\".\n// Notification entries are dropped on overflow per the same criterion.\n//\n// Rate limiting: each chat gets a token bucket that paces sends according\n// to Telegram's per-chat rate limits. The default policy (0.33 tokens/s,\n// burst 4) is conservative for group chats while permitting short notification\n// bursts to drain immediately; private chats use a higher rate (30 tokens/s,\n// burst 5) when the chat type is known.\n// ---------------------------------------------------------------------------\n\nimport type { Logger } from '@wrongstack/core/types';\nimport type { TelegramApiMessage } from './api-client.js';\nimport type { TelegramBot, TelegramBotResponse } from './bot.js';\nimport { type OutboundEntry, OutboundQueue } from './outbound-queue.js';\nimport { createTokenBucket, type TokenBucket } from './rate-limiter.js';\n\nexport interface BotOutboundOptions {\n readonly bot: TelegramBot;\n /** Pass-through logger (defaults to bot's internal logger via the bot's debug hook). */\n readonly log: Logger;\n /** Optional override; defaults to 32 entries per chat. */\n readonly maxPerChat?: number;\n /** Optional override; defaults to 4 concurrent sends. */\n readonly maxConcurrency?: number;\n /**\n * Live getter for tokens per second on the per-chat rate limiter.\n * Default: 0.33 (\u224820 messages/minute, safe for groups).\n * Private chats can safely use 30.\n * Passed as a getter so config hot-reload takes effect on the next new\n * bucket (existing buckets retain their captured parameters for the\n * queue lifetime, which is the intended lifetime of the bot).\n */\n readonly getRateLimitTokensPerSecond?: (() => number) | undefined;\n /**\n * Live getter for maximum burst size on the per-chat rate limiter.\n * Default: 4. Read lazily per new-bucket creation so config\n * hot-reload propagates without queue rebuild.\n */\n readonly getRateLimitBurst?: (() => number) | undefined;\n}\n\n/** How often idle, fully-refilled per-chat buckets are swept from the map. */\nconst BUCKET_SWEEP_INTERVAL_MS = 60_000;\n\nexport class TelegramBotOutbound {\n readonly #queue: OutboundQueue;\n readonly #bot: TelegramBot;\n readonly #log: Logger;\n readonly #buckets = new Map<string, TokenBucket>();\n readonly #getRateTokensPerSecond: () => number;\n readonly #getRateBurst: () => number;\n readonly #bucketSweepTimer: ReturnType<typeof setInterval>;\n #stopped = false;\n\n constructor(opts: BotOutboundOptions) {\n this.#bot = opts.bot;\n this.#log = opts.log;\n this.#getRateTokensPerSecond = opts.getRateLimitTokensPerSecond ?? (() => 0.33);\n this.#getRateBurst = opts.getRateLimitBurst ?? (() => 4);\n this.#queue = new OutboundQueue({\n maxPerChat: opts.maxPerChat,\n maxConcurrency: opts.maxConcurrency,\n send: (chatId, text) => this.#rateLimitedSend(chatId, text),\n log: opts.log,\n });\n // Without this the bucket map grows one entry per distinct chatId for the\n // whole process lifetime (a bot reachable by many chats leaks memory).\n // Only fully-refilled buckets are evicted: they carry no pacing state, so\n // the next send to that chat lazily recreates an identical full bucket.\n // A depleted (actively rate-limited) bucket is never full, so its pacing\n // state always survives the sweep.\n this.#bucketSweepTimer = setInterval(() => this.#sweepIdleBuckets(), BUCKET_SWEEP_INTERVAL_MS);\n this.#bucketSweepTimer.unref?.();\n }\n\n #sweepIdleBuckets(): void {\n for (const [key, bucket] of this.#buckets) {\n if (bucket.isFull()) this.#buckets.delete(key);\n }\n }\n\n /** Per-chat rate-limited send: waits for a token, then delegates to the bot.\n * Rate-limit values are resolved lazily when a new bucket is created via\n * the constructor getters so a live config change applies to subsequent\n * chats without rebuilding the queue. */\n async #rateLimitedSend(\n chatId: string | number,\n text: string,\n ): Promise<TelegramBotResponse<TelegramApiMessage>> {\n const key = String(chatId);\n let bucket = this.#buckets.get(key);\n if (!bucket) {\n bucket = createTokenBucket({\n tokensPerSecond: this.#getRateTokensPerSecond(),\n burst: this.#getRateBurst(),\n });\n this.#buckets.set(key, bucket);\n }\n\n // Wait up to 5s for a token slot; if timeout, proceed anyway so the\n // queue doesn't stall. Telegram will return 429 if we're still over the\n // limit, and the api-client's retry logic will handle it.\n await bucket.waitForToken(5_000);\n\n const res = await this.#bot.sendMessage(chatId, text);\n if (!res.ok) {\n throw new Error(`Telegram outbound send returned ok=false for chat ${chatId}`);\n }\n return res;\n }\n\n /** Manual send (telegram_send tool, /telegram:send): never silently dropped. */\n async sendManual(\n chatId: string | number,\n text: string,\n ): Promise<TelegramBotResponse<TelegramApiMessage>> {\n if (this.#stopped) {\n throw new Error('Telegram outbound queue is stopped');\n }\n return (await this.#queue.enqueue({\n chatId,\n text,\n kind: 'manual',\n })) as TelegramBotResponse<TelegramApiMessage>;\n }\n\n /**\n * Notification send (session ended, long tool, delegate): fire-and-forget.\n * The returned promise resolves as soon as the queue accepts the entry;\n * downstream send failures are logged and counted but not surfaced.\n */\n enqueueNotification(chatId: string | number, text: string): void {\n if (this.#stopped) {\n this.#log.debug(`Telegram outbound queue ignored notification for chat ${chatId}: stopped`);\n return;\n }\n const entry: OutboundEntry = { chatId, text, kind: 'notification' };\n // Notifications are accepted or dropped without rejection. The outer\n // stopped guard prevents the only rejecting state of the inner queue.\n void this.#queue.enqueue(entry);\n }\n\n stats() {\n return this.#queue.stats();\n }\n\n async stop(): Promise<void> {\n this.#stopped = true;\n clearInterval(this.#bucketSweepTimer);\n await this.#queue.stop();\n }\n}\n", "/**\n * TelegramNotificationChannel \u2014 NotificationChannel implementation for\n * one-way Telegram message delivery.\n *\n * This is the **notification-only** path: fire-and-forget messages sent to\n * a configured chat when the Notifier routes a `NotificationMessage` to\n * the `\"telegram\"` channel. It wraps `bot.sendMessage()` with the standard\n * scrubbing and truncation pipeline used by every outgoing Telegram message.\n *\n * This channel does NOT handle:\n * - 2-way communication (telegram_read, telegram_approve, inline keyboards)\n * - Manual sends via the telegram_send tool (those go through\n * `TelegramBotOutbound.sendManual()` for queue ordering + error surfacing)\n * - Slash commands or polling\n *\n * Those remain in the main `@wrongstack/telegram` plugin, which continues\n * to own the TelegramBot instance, the inbound poller, the outbound queue\n * for manual sends, and the system prompt contributor.\n *\n * @module telegram\n * @public\n */\n\nimport type { Logger } from '@wrongstack/core/types';\nimport type {\n NotificationChannel,\n NotificationLevel,\n NotificationMessage,\n NotificationResult,\n} from '@wrongstack/core/notifications';\nimport type { TelegramBot } from './bot.js';\nimport { scrubTelegramOutboundText } from './security/outbound.js';\nimport { truncateForTelegram } from './bot.js';\n\n// ---------------------------------------------------------------------------\n// Level \u2192 emoji mapping for Telegram preview\n// ---------------------------------------------------------------------------\n\nconst LEVEL_ICON: Record<NotificationLevel, string> = {\n info: '\u2139\uFE0F',\n warning: '\u26A0\uFE0F',\n critical: '\uD83D\uDEA8',\n};\n\n// ---------------------------------------------------------------------------\n// Channel\n// ---------------------------------------------------------------------------\n\nexport interface TelegramNotificationChannelOptions {\n /** The TelegramBot instance (owned by the main plugin). */\n readonly bot: TelegramBot;\n /** Queue-backed notification sender used by the plugin runtime. */\n readonly enqueueNotification?:\n | ((chatId: string | number, text: string) => void)\n | undefined;\n /** Target chat or user ID for all notifications sent through this channel. */\n readonly chatId: string | number;\n /**\n * Maximum message length in characters. Default 4000 (Telegram's hard\n * cap is 4096; `truncateForTelegram` clamps internally). */\n readonly maxMessageLength?: number | undefined;\n /** Logger for debug-level diagnostics. */\n readonly log?: Logger | undefined;\n}\n\nexport class TelegramNotificationChannel implements NotificationChannel {\n readonly name = 'telegram' as const;\n readonly type = 'telegram' as const;\n\n readonly #bot: TelegramBot;\n readonly #chatId: string | number;\n readonly #enqueueNotification:\n | ((chatId: string | number, text: string) => void)\n | undefined;\n readonly #maxLen: number;\n readonly #log: Logger | undefined;\n\n constructor(opts: TelegramNotificationChannelOptions) {\n this.#bot = opts.bot;\n this.#chatId = opts.chatId;\n this.#enqueueNotification = opts.enqueueNotification;\n this.#maxLen = opts.maxMessageLength ?? 4000;\n this.#log = opts.log;\n }\n\n /**\n * Deliver a notification message to the configured Telegram chat.\n *\n * Renders the `NotificationMessage` into a single Telegram text message:\n * - Prepends a level-based emoji icon (\u2139\uFE0F / \u26A0\uFE0F / \uD83D\uDEA8)\n * - Combines `title` (when present) and `body`\n * - Runs through credential scrubbing\n * - Truncates to the configured max length\n *\n * **Does not throw.** Transport errors are caught and returned as\n * `{ ok: false, error: \"\u2026\" }`.\n */\n async deliver(msg: NotificationMessage): Promise<NotificationResult> {\n const deliveredAt = new Date().toISOString();\n try {\n // 1. Render the message for Telegram with level-based icon\n const icon = LEVEL_ICON[msg.level] ?? LEVEL_ICON.info;\n const parts: string[] = [];\n if (msg.title) parts.push(msg.title);\n parts.push(msg.body);\n const rawText = `${icon} ${parts.join('\\n')}`;\n\n // 2. Scrub credentials \u2014 runs BEFORE truncation so a credential\n // is never split across the boundary where the pattern can't match.\n const scrubbed = scrubTelegramOutboundText(rawText);\n\n // 3. Truncate to fit Telegram's message size limit\n const truncated = truncateForTelegram(scrubbed, this.#maxLen);\n\n // 4. Queue when the plugin runtime supplies its bounded outbound path.\n if (this.#enqueueNotification) {\n this.#enqueueNotification(this.#chatId, truncated);\n this.#log?.debug?.(`telegram notification queued (${truncated.length} chars)`);\n return { ok: true, channel: this.name, deliveredAt };\n }\n\n // Standalone channels retain direct delivery for backwards compatibility.\n const res = await this.#bot.sendMessage(this.#chatId, truncated);\n\n this.#log?.debug?.(`telegram notification delivered (${truncated.length} chars, ok=${res.ok})`);\n\n return {\n ok: res.ok,\n channel: this.name,\n ...(res.ok ? {} : { error: `Telegram API returned ok=false` }),\n deliveredAt,\n };\n } catch (err) {\n this.#log?.debug?.(\n `telegram notification delivery failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return {\n ok: false,\n channel: this.name,\n error: err instanceof Error ? err.message : String(err),\n deliveredAt,\n };\n }\n }\n\n /**\n * Liveness probe \u2014 delegates to the bot's health check.\n * Returns `{ ok: true }` when the bot token is valid and\n * api.telegram.org is reachable.\n */\n async ping(): Promise<{ ok: boolean; error?: string | undefined }> {\n try {\n const h = await this.#bot.health();\n return { ok: h.ok, ...(h.ok ? {} : { error: h.error ?? 'health check failed' }) };\n } catch (err) {\n return {\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n", "import type { Tool } from '@wrongstack/core/types';\nimport type { TelegramBot } from '../bot.js';\n\ninterface TelegramReadInput {\n /** Filter to messages from a specific chat/user ID. Omit to see all chats. */\n chat_id?: string | number | undefined;\n /** Max messages to return (default: 10, max: 50). */\n limit?: number | undefined;\n /**\n * If a message_id is provided, acknowledge all messages up to and\n * including this ID (mark them as processed / remove from buffer).\n */\n ack_last?: number | undefined;\n}\n\nexport function makeTelegramReadTool(opts: {\n bot: TelegramBot;\n}): Tool<TelegramReadInput> {\n return {\n name: 'telegram_read',\n description:\n 'Read recent incoming Telegram messages the bot has received, newest first. Returns messages with sender, text, and timestamp. After reading, acknowledge them with ack_last so they are cleared. When responding to a user via telegram_send, format your reply as natural prose \u2014 summarize findings, report outcomes clearly, do not paste raw data.',\n usageHint: 'telegram_read(chat_id: \"123456789\", limit: 5, ack_last: 42) \u2014 read messages, then ack the highest message_id to clear them.',\n category: 'Telegram',\n inputSchema: {\n type: 'object',\n properties: {\n chat_id: {\n oneOf: [{ type: 'string' }, { type: 'integer' }],\n description: 'Read messages only from this chat/user.',\n },\n limit: {\n type: 'integer',\n minimum: 1,\n maximum: 50,\n description: 'Max messages to return (default: 10).',\n },\n ack_last: {\n type: 'integer',\n description:\n 'After processing messages, pass the highest message_id to clear them from the buffer.',\n },\n },\n },\n permission: 'auto',\n mutating: false,\n timeoutMs: 5_000,\n async execute(input) {\n const msgs = opts.bot.getMessages({\n chatId: input.chat_id,\n limit: input.limit ?? 10,\n });\n\n let acked = 0;\n if (input.ack_last !== undefined && input.ack_last > 0) {\n acked = opts.bot.acknowledge(input.ack_last);\n }\n\n return {\n buffer_total: opts.bot.bufferCount,\n messages: msgs.map((m) => ({\n message_id: m.messageId,\n chat_id: m.chatId,\n chat_type: m.chatType,\n from: m.userName ?? `user_${m.userId ?? 'unknown'}`,\n text: m.text,\n ts: new Date(m.timestamp).toISOString(),\n })),\n acked,\n hint: acked > 0\n ? undefined\n : 'Use ack_last with the highest message_id to clear processed messages.',\n };\n },\n };\n}\n", "import { ToolCapabilities } from '@wrongstack/core/security';\nimport type { Logger, Tool } from '@wrongstack/core/types';\nimport type { TelegramBot } from '../bot.js';\nimport {\n resolveTelegramOutboundTarget,\n scrubTelegramOutboundText,\n type TelegramChatId,\n} from '../security/outbound.js';\nimport { truncateForTelegram } from '../bot.js';\n\ninterface TelegramSendInput {\n /** Chat or user ID to send the message to. Falls back to config.notifyChatId when omitted. */\n chat_id?: string | number | undefined;\n /** Message text. */\n message: string;\n}\n\nexport function makeTelegramSendTool(opts: {\n bot: TelegramBot;\n /** Paired/default target, resolved on every call for live config updates. */\n getDefaultChatId(): TelegramChatId | undefined;\n /** Additional trusted targets, resolved on every call for live config updates. */\n getAllowedOutboundChatIds?(): readonly TelegramChatId[];\n maxMessageLength: number;\n log: Logger;\n}): Tool<TelegramSendInput> {\n return {\n name: 'telegram_send',\n description:\n 'Send a scrubbed message to the paired Telegram chat or an explicitly allowed outbound chat. Write natural prose for a human reader; summarize results and never paste raw JSON, object dumps, credentials, or truncated tool output.',\n usageHint:\n 'telegram_send(chat_id: \"123456789\", message: \"Build completed \u2014 12 tests passed, 0 failed. Deploying to staging now.\")',\n category: 'Telegram',\n inputSchema: {\n type: 'object',\n properties: {\n chat_id: {\n oneOf: [{ type: 'string' }, { type: 'integer' }],\n description: 'Target chat or user ID. Uses the plugin default when omitted.',\n },\n message: {\n type: 'string',\n description:\n 'Message text in natural, human-readable prose. Summarize results, include only key details. Do NOT paste raw JSON, object dumps, or unformatted tool output. Target 1\u20134 lines for readability on mobile.',\n },\n },\n required: ['message'],\n },\n permission: 'confirm',\n mutating: true,\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n timeoutMs: 15_000,\n async execute(input, _ctx, toolOpts) {\n const chatId = resolveTelegramOutboundTarget(input.chat_id, opts);\n\n // Scrub before truncation so a credential is never split into fragments\n // that no longer match the shared detector.\n const scrubbed = scrubTelegramOutboundText(input.message);\n const truncated = truncateForTelegram(scrubbed, opts.maxMessageLength);\n\n opts.log.info(`telegram_send \u2192 chat_id=${chatId} (${truncated.length} chars)`);\n\n const res = toolOpts?.signal\n ? await opts.bot.sendMessage(chatId, truncated, toolOpts.signal)\n : await opts.bot.sendMessage(chatId, truncated);\n\n return {\n ok: res.ok,\n message_id: res.result?.message_id,\n chat: res.result?.chat\n ? {\n id: res.result.chat.id,\n type: res.result.chat.type,\n title: res.result.chat.title,\n }\n : undefined,\n };\n },\n };\n}\n", "// ---------------------------------------------------------------------------\n// P2.2 \u2014 Classify each Telegram runtime config key as either\n// \"hot-reload-safe\" or \"requires restart\".\n//\n// The plugin can apply changes live for keys marked HOT. Anything else\n// (identity, transport, capacity) touches the long-lived bot, the\n// outbound-queue worker, or the poll lock \u2014 so it must surface a restart\n// hint to the operator rather than silently misbehave.\n//\n// This module is a compatibility facade over the Core lifecycle metadata:\n// no fs, no network, no side effects. TELEGRAM_CONFIG_FIELDS is the single\n// source of truth and is exhaustive at the type level. Unknown keys are\n// defaulted to 'immutable' inside diffPluginConfig (Core); anything not\n// 'hot' (i.e. both 'restart' and 'immutable') is folded to 'restart-required'\n// here, so a newly added or restart-required field stays restart-required\n// until explicitly marked HOT.\n// ---------------------------------------------------------------------------\n\nimport { diffPluginConfig } from '@wrongstack/core/plugin';\nimport { TELEGRAM_CONFIG_FIELDS, type TelegramPluginConfig } from './config.js';\n\nexport type ConfigReloadClass = 'hot' | 'restart-required';\n\n/**\n * Diff two configs and return the keys whose values differ, each tagged\n * with its reload classification. Keys present in `previous` but absent in\n * `next` (or vice versa) are reported as `'restart-required'` so a stale\n * removal is never silently applied as a live change.\n */\nexport function diffConfigKeys(\n previous: TelegramPluginConfig,\n next: TelegramPluginConfig,\n): Array<{ key: keyof TelegramPluginConfig; classification: ConfigReloadClass }> {\n // Double cast: TelegramPluginConfig has optional fields, so its structural\n // type is not assignable to Record<string, unknown> directly. The key cast\n // below is safe because diffPluginConfig only iterates Object.keys(prev) \u222A Object.keys(next).\n return diffPluginConfig(\n previous as unknown as Record<string, unknown>,\n next as unknown as Record<string, unknown>,\n TELEGRAM_CONFIG_FIELDS,\n ).map((change) => ({\n key: change.key as keyof TelegramPluginConfig,\n classification: change.lifecycle === 'hot' ? 'hot' : 'restart-required',\n }));\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["expectDefined", "delayMs", "createHash", "mkdirSync", "readFileSync", "renameSync", "unlinkSync", "dirname", "join", "wstackGlobalRoot", "res", "randomUUID", "randomUUID", "expectDefined"]
|
|
7
7
|
}
|