@wrongstack/telegram 0.291.1 → 0.292.0

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/index.d.ts CHANGED
@@ -12,4 +12,6 @@ export default plugin;
12
12
  export { teardownState };
13
13
  export type { TelegramIncomingMessage } from './bot.js';
14
14
  export type { TelegramPluginConfig } from './config.js';
15
+ export { TelegramNotificationChannel } from './notification-channel.js';
16
+ export type { TelegramNotificationChannelOptions } from './notification-channel.js';
15
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,MAAM,EAA2B,MAAM,kBAAkB,CAAC;AAGxF,OAAO,EAAE,WAAW,EAAuB,MAAM,UAAU,CAAC;AAS5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAuBrD,UAAU,YAAY;IACpB,GAAG,EAAE,WAAW,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;CAC7B;AAED,QAAA,IAAI,aAAa,EAAE,YAAY,GAAG,IAAW,CAAC;AA+F9C,QAAA,MAAM,MAAM,EAAE,MA6Rb,CAAC;eAEa,MAAM;AAGrB,OAAO,EAAE,aAAa,EAAE,CAAC;AAGzB,YAAY,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,MAAM,EAA2B,MAAM,kBAAkB,CAAC;AAGxF,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AASvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAwBrD,UAAU,YAAY;IACpB,GAAG,EAAE,WAAW,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;CAC7B;AAED,QAAA,IAAI,aAAa,EAAE,YAAY,GAAG,IAAW,CAAC;AA+F9C,QAAA,MAAM,MAAM,EAAE,MAyUb,CAAC;eAEa,MAAM;AAGrB,OAAO,EAAE,aAAa,EAAE,CAAC;AAGzB,YAAY,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACxE,YAAY,EAAE,kCAAkC,EAAE,MAAM,2BAA2B,CAAC"}
package/dist/index.js CHANGED
@@ -1726,6 +1726,92 @@ var TelegramBotOutbound = class {
1726
1726
  }
1727
1727
  };
1728
1728
 
1729
+ // src/notification-channel.ts
1730
+ var LEVEL_ICON = {
1731
+ info: "\u2139\uFE0F",
1732
+ warning: "\u26A0\uFE0F",
1733
+ critical: "\u{1F6A8}"
1734
+ };
1735
+ var TelegramNotificationChannel = class {
1736
+ name = "telegram";
1737
+ type = "telegram";
1738
+ #bot;
1739
+ #chatId;
1740
+ #enqueueNotification;
1741
+ #maxLen;
1742
+ #log;
1743
+ constructor(opts) {
1744
+ this.#bot = opts.bot;
1745
+ this.#chatId = opts.chatId;
1746
+ this.#enqueueNotification = opts.enqueueNotification;
1747
+ this.#maxLen = opts.maxMessageLength ?? 4e3;
1748
+ this.#log = opts.log;
1749
+ }
1750
+ /**
1751
+ * Deliver a notification message to the configured Telegram chat.
1752
+ *
1753
+ * Renders the `NotificationMessage` into a single Telegram text message:
1754
+ * - Prepends a level-based emoji icon (ℹ️ / ⚠️ / 🚨)
1755
+ * - Combines `title` (when present) and `body`
1756
+ * - Runs through credential scrubbing
1757
+ * - Truncates to the configured max length
1758
+ *
1759
+ * **Does not throw.** Transport errors are caught and returned as
1760
+ * `{ ok: false, error: "…" }`.
1761
+ */
1762
+ async deliver(msg) {
1763
+ const deliveredAt = (/* @__PURE__ */ new Date()).toISOString();
1764
+ try {
1765
+ const icon = LEVEL_ICON[msg.level] ?? LEVEL_ICON.info;
1766
+ const parts = [];
1767
+ if (msg.title) parts.push(msg.title);
1768
+ parts.push(msg.body);
1769
+ const rawText = `${icon} ${parts.join("\n")}`;
1770
+ const scrubbed = scrubTelegramOutboundText(rawText);
1771
+ const truncated = truncateForTelegram(scrubbed, this.#maxLen);
1772
+ if (this.#enqueueNotification) {
1773
+ this.#enqueueNotification(this.#chatId, truncated);
1774
+ this.#log?.debug?.(`telegram notification queued (${truncated.length} chars)`);
1775
+ return { ok: true, channel: this.name, deliveredAt };
1776
+ }
1777
+ const res = await this.#bot.sendMessage(this.#chatId, truncated);
1778
+ this.#log?.debug?.(`telegram notification delivered (${truncated.length} chars, ok=${res.ok})`);
1779
+ return {
1780
+ ok: res.ok,
1781
+ channel: this.name,
1782
+ ...res.ok ? {} : { error: `Telegram API returned ok=false` },
1783
+ deliveredAt
1784
+ };
1785
+ } catch (err) {
1786
+ this.#log?.debug?.(
1787
+ `telegram notification delivery failed: ${err instanceof Error ? err.message : String(err)}`
1788
+ );
1789
+ return {
1790
+ ok: false,
1791
+ channel: this.name,
1792
+ error: err instanceof Error ? err.message : String(err),
1793
+ deliveredAt
1794
+ };
1795
+ }
1796
+ }
1797
+ /**
1798
+ * Liveness probe — delegates to the bot's health check.
1799
+ * Returns `{ ok: true }` when the bot token is valid and
1800
+ * api.telegram.org is reachable.
1801
+ */
1802
+ async ping() {
1803
+ try {
1804
+ const h = await this.#bot.health();
1805
+ return { ok: h.ok, ...h.ok ? {} : { error: h.error ?? "health check failed" } };
1806
+ } catch (err) {
1807
+ return {
1808
+ ok: false,
1809
+ error: err instanceof Error ? err.message : String(err)
1810
+ };
1811
+ }
1812
+ }
1813
+ };
1814
+
1729
1815
  // src/tools/telegram-read.ts
1730
1816
  function makeTelegramReadTool(opts) {
1731
1817
  return {
@@ -2014,9 +2100,20 @@ var plugin = {
2014
2100
  ]) {
2015
2101
  registerCommand(api, command, cleanups);
2016
2102
  }
2103
+ let notifyChannel;
2104
+ if (runtimeCfg.notifyChatId !== void 0) {
2105
+ notifyChannel = new TelegramNotificationChannel({
2106
+ bot,
2107
+ chatId: runtimeCfg.notifyChatId,
2108
+ maxMessageLength: runtimeCfg.maxMessageLength,
2109
+ enqueueNotification: (chatId, text) => outbound.enqueueNotification(chatId, text),
2110
+ log
2111
+ });
2112
+ api.notifier?.registerChannel(notifyChannel);
2113
+ }
2017
2114
  cleanups.push(
2018
2115
  api.events.on("session.ended", (event) => {
2019
- if (!runtimeCfg.notifyOnSessionEnd || !runtimeCfg.notifyChatId) return;
2116
+ if (!runtimeCfg.notifyOnSessionEnd || !runtimeCfg.notifyChatId || !notifyChannel) return;
2020
2117
  const payload = {
2021
2118
  id: scrubTelegramOutboundText(event.id),
2022
2119
  inputTokens: event.usage.input,
@@ -2024,33 +2121,39 @@ var plugin = {
2024
2121
  cacheRead: event.usage.cacheRead,
2025
2122
  cacheWrite: event.usage.cacheWrite
2026
2123
  };
2027
- const msg = truncateForTelegram(
2028
- scrubTelegramOutboundText(formatSessionEnded(payload)),
2029
- runtimeCfg.maxMessageLength
2030
- );
2031
- outbound.enqueueNotification(expectDefined2(runtimeCfg.notifyChatId), msg);
2124
+ notifyChannel.deliver({
2125
+ title: "Session ended",
2126
+ body: formatSessionEnded(payload),
2127
+ level: "info",
2128
+ source: "session.end"
2129
+ }).then((r) => {
2130
+ if (!r.ok) log.warn(`session.ended notification delivery failed: ${r.error ?? "unknown"}`);
2131
+ });
2032
2132
  })
2033
2133
  );
2034
2134
  cleanups.push(
2035
2135
  api.events.on("tool.executed", (event) => {
2036
- if (!runtimeCfg.notifyChatId || runtimeCfg.longToolThresholdMs <= 0 || event.durationMs < runtimeCfg.longToolThresholdMs)
2037
- return;
2136
+ if (!runtimeCfg.notifyChatId || !notifyChannel || runtimeCfg.longToolThresholdMs <= 0) return;
2137
+ if (event.durationMs < runtimeCfg.longToolThresholdMs) return;
2038
2138
  const payload = {
2039
2139
  name: event.name,
2040
2140
  ok: event.ok,
2041
2141
  durationMs: event.durationMs,
2042
2142
  output: event.output === void 0 ? void 0 : scrubTelegramOutboundText(event.output)
2043
2143
  };
2044
- const msg = truncateForTelegram(
2045
- scrubTelegramOutboundText(formatToolExecuted(payload)),
2046
- runtimeCfg.maxMessageLength
2047
- );
2048
- outbound.enqueueNotification(expectDefined2(runtimeCfg.notifyChatId), msg);
2144
+ notifyChannel.deliver({
2145
+ title: event.ok ? "Tool completed" : "Tool failed",
2146
+ body: formatToolExecuted(payload),
2147
+ level: event.ok ? "info" : "warning",
2148
+ source: "tool.exec"
2149
+ }).then((r) => {
2150
+ if (!r.ok) log.warn(`tool.executed notification delivery failed: ${r.error ?? "unknown"}`);
2151
+ });
2049
2152
  })
2050
2153
  );
2051
2154
  cleanups.push(
2052
2155
  api.events.on("delegate.completed", (event) => {
2053
- if (!runtimeCfg.notifyOnDelegate || !runtimeCfg.notifyChatId) return;
2156
+ if (!runtimeCfg.notifyOnDelegate || !runtimeCfg.notifyChatId || !notifyChannel) return;
2054
2157
  const safeEvent = {
2055
2158
  ...event,
2056
2159
  target: scrubTelegramOutboundText(event.target),
@@ -2058,15 +2161,19 @@ var plugin = {
2058
2161
  status: event.status === void 0 ? void 0 : scrubTelegramOutboundText(event.status),
2059
2162
  summary: scrubTelegramOutboundText(event.summary)
2060
2163
  };
2061
- const msg = truncateForTelegram(
2062
- scrubTelegramOutboundText(formatDelegateCompleted(safeEvent)),
2063
- runtimeCfg.maxMessageLength
2064
- );
2065
- outbound.enqueueNotification(expectDefined2(runtimeCfg.notifyChatId), msg);
2164
+ notifyChannel.deliver({
2165
+ title: `Delegate: ${safeEvent.target}`,
2166
+ body: formatDelegateCompleted(safeEvent),
2167
+ level: event.ok ? "info" : "warning",
2168
+ source: "delegate.completed"
2169
+ }).then((r) => {
2170
+ if (!r.ok) log.warn(`delegate.completed notification delivery failed: ${r.error ?? "unknown"}`);
2171
+ });
2066
2172
  })
2067
2173
  );
2068
- const unlistenConfig = api.onConfigChange((next, _prev) => {
2174
+ const unlistenConfig = api.onConfigChange((next, prev) => {
2069
2175
  const fresh = telegramFromConfig(next);
2176
+ const was = telegramFromConfig(prev);
2070
2177
  runtimeCfg.notifyChatId = fresh.notifyChatId;
2071
2178
  runtimeCfg.allowedOutboundChats = fresh.allowedOutboundChats;
2072
2179
  runtimeCfg.allowedUserIds = fresh.allowedUserIds;
@@ -2075,6 +2182,15 @@ var plugin = {
2075
2182
  runtimeCfg.notifyOnDelegate = fresh.notifyOnDelegate;
2076
2183
  runtimeCfg.longToolThresholdMs = fresh.longToolThresholdMs;
2077
2184
  runtimeCfg.maxMessageLength = fresh.maxMessageLength;
2185
+ if (fresh.notifyChatId !== was.notifyChatId || fresh.maxMessageLength !== was.maxMessageLength) {
2186
+ notifyChannel = fresh.notifyChatId !== void 0 ? new TelegramNotificationChannel({
2187
+ bot,
2188
+ chatId: fresh.notifyChatId,
2189
+ maxMessageLength: fresh.maxMessageLength,
2190
+ enqueueNotification: (chatId, text) => outbound.enqueueNotification(chatId, text),
2191
+ log
2192
+ }) : void 0;
2193
+ }
2078
2194
  log.debug("Telegram notification settings updated from config", {
2079
2195
  notifyOnSessionEnd: runtimeCfg.notifyOnSessionEnd,
2080
2196
  notifyOnDelegate: runtimeCfg.notifyOnDelegate,
@@ -2106,6 +2222,7 @@ var plugin = {
2106
2222
  };
2107
2223
  var src_default = plugin;
2108
2224
  export {
2225
+ TelegramNotificationChannel,
2109
2226
  src_default as default,
2110
2227
  teardownState
2111
2228
  };
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 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/bot-queue.ts", "../src/tools/telegram-read.ts", "../src/tools/telegram-send.ts"],
4
- "sourcesContent": ["import type { Config, Logger, Plugin, PluginAPI, SlashCommand } from '@wrongstack/core';\nimport { expectDefined } from '@wrongstack/core';\nimport type { TelegramIncomingMessage } from './bot.js';\nimport { TelegramBot, truncateForTelegram } from './bot.js';\nimport { PLUGIN_NAME, readTelegramConfig, telegramConfigSchema } 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 { makeTelegramReadTool } from './tools/telegram-read.js';\nimport { makeTelegramSendTool } from './tools/telegram-send.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}\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/** Read the Telegram section from a full Config object. */\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} {\n const ext =\n (cfg.extensions as Record<string, Record<string, unknown>> | undefined)?.[PLUGIN_NAME] ?? {};\n return {\n notifyChatId: ext.notifyChatId !== undefined ? String(ext.notifyChatId) : undefined,\n allowedOutboundChats: Array.isArray(ext.allowedOutboundChats)\n ? ext.allowedOutboundChats.filter(\n (chatId): chatId is string | number =>\n typeof chatId === 'string' || typeof chatId === 'number',\n )\n : [],\n allowedUserIds: Array.isArray(ext.allowedUsers)\n ? ext.allowedUsers.filter(\n (userId): userId is string | number =>\n typeof userId === 'string' || typeof userId === 'number',\n )\n : [],\n allowGroupApprovals: ext.allowGroupApprovals === true,\n notifyOnSessionEnd: ext.notifyOnSessionEnd === true,\n notifyOnDelegate: ext.notifyOnDelegate !== false, // default true\n longToolThresholdMs:\n typeof ext.longToolThresholdMs === 'number' ? ext.longToolThresholdMs : 30_000,\n maxMessageLength: typeof ext.maxMessageLength === 'number' ? ext.maxMessageLength : 4000,\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 configSchema: telegramConfigSchema,\n defaultConfig: {\n allowedOutboundChats: [],\n pollIntervalSec: 2,\n notifyOnSessionEnd: false,\n longToolThresholdMs: 30_000,\n maxMessageLength: 4000,\n },\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 rawCfg = cfg as ReturnType<typeof readTelegramConfig> & {\n allowGroupApprovals?: boolean | undefined;\n };\n const runtimeCfg: RuntimeConfig = {\n notifyChatId: cfg.notifyChatId,\n allowedOutboundChats: [...(cfg.allowedOutboundChats ?? [])],\n allowedUserIds: [...(cfg.allowedUsers ?? [])],\n allowGroupApprovals: rawCfg.allowGroupApprovals === true,\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 };\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 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 // 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 const outbound = new TelegramBotOutbound({\n bot,\n log,\n maxPerChat: runtimeCfg.outboundQueuePerChat,\n maxConcurrency: runtimeCfg.outboundQueueConcurrency,\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 allowGroupApprovals: 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 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\n cleanups.push(\n api.events.on('session.ended', (event) => {\n if (!runtimeCfg.notifyOnSessionEnd || !runtimeCfg.notifyChatId) 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 const msg = truncateForTelegram(\n scrubTelegramOutboundText(formatSessionEnded(payload)),\n runtimeCfg.maxMessageLength,\n );\n outbound.enqueueNotification(expectDefined(runtimeCfg.notifyChatId), msg);\n }),\n );\n\n cleanups.push(\n api.events.on('tool.executed', (event) => {\n if (\n !runtimeCfg.notifyChatId ||\n runtimeCfg.longToolThresholdMs <= 0 ||\n event.durationMs < runtimeCfg.longToolThresholdMs\n )\n 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 const msg = truncateForTelegram(\n scrubTelegramOutboundText(formatToolExecuted(payload)),\n runtimeCfg.maxMessageLength,\n );\n outbound.enqueueNotification(expectDefined(runtimeCfg.notifyChatId), msg);\n }),\n );\n\n cleanups.push(\n api.events.on('delegate.completed', (event) => {\n if (!runtimeCfg.notifyOnDelegate || !runtimeCfg.notifyChatId) 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 const msg = truncateForTelegram(\n scrubTelegramOutboundText(formatDelegateCompleted(safeEvent)),\n runtimeCfg.maxMessageLength,\n );\n outbound.enqueueNotification(expectDefined(runtimeCfg.notifyChatId), msg);\n }),\n );\n\n // ---- Live config updates ----\n // api.config is frozen at setup, but onConfigChange fires whenever the\n // ConfigStore is updated (from CLI /settings, WebUI prefSync, /telegram-settings).\n // Update the mutable runtime refs so all handlers pick up the new values\n // on the next event \u2014 no restart needed.\n const unlistenConfig = api.onConfigChange((next, _prev) => {\n const fresh = telegramFromConfig(next);\n runtimeCfg.notifyChatId = fresh.notifyChatId;\n runtimeCfg.allowedOutboundChats = fresh.allowedOutboundChats;\n runtimeCfg.allowedUserIds = fresh.allowedUserIds;\n runtimeCfg.allowGroupApprovals = fresh.allowGroupApprovals;\n runtimeCfg.notifyOnSessionEnd = fresh.notifyOnSessionEnd;\n runtimeCfg.notifyOnDelegate = fresh.notifyOnDelegate;\n runtimeCfg.longToolThresholdMs = fresh.longToolThresholdMs;\n runtimeCfg.maxMessageLength = fresh.maxMessageLength;\n log.debug('Telegram notification settings updated from config', {\n notifyOnSessionEnd: runtimeCfg.notifyOnSessionEnd,\n notifyOnDelegate: runtimeCfg.notifyOnDelegate,\n longToolThresholdMs: runtimeCfg.longToolThresholdMs,\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 consumers may want\nexport type { TelegramIncomingMessage } from './bot.js';\nexport type { TelegramPluginConfig } from './config.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}\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 return this.request<TelegramApiMessage>('sendMessage', {\n body: {\n chat_id: String(chatId),\n text,\n disable_web_page_preview: true,\n },\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 return this.request<TelegramApiMessage>('sendMessage', {\n body: {\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 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';\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 /** 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 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 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 });\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 });\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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n", "import type { PluginAPI } from '@wrongstack/core';\n\nexport const PLUGIN_NAME = 'telegram';\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}\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};\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 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 },\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 config = api.config as never as Record<string, unknown>;\n const extensions = config.extensions as Record<string, unknown> | undefined;\n const pluginEntries = config.plugins;\n const legacyPlugins = pluginEntries as Record<string, unknown> | undefined;\n const legacyOpts =\n legacyPlugins && !Array.isArray(legacyPlugins) ? legacyPlugins[PLUGIN_NAME] : undefined;\n const entryOpts = pluginOptionsFromEntries(pluginEntries);\n const extensionOpts = extensions?.[PLUGIN_NAME];\n const opts = {\n ...((legacyOpts ?? entryOpts) as TelegramPluginConfig),\n ...((extensionOpts ?? {}) as TelegramPluginConfig),\n };\n const inboundMode = resolveInboundMode(opts, {\n configured: legacyOpts !== undefined || entryOpts !== undefined || extensionOpts !== undefined,\n warn: api.log?.warn.bind(api.log),\n });\n\n return {\n ...DEFAULT_CONFIG,\n ...opts,\n inboundMode,\n };\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\nfunction pluginOptionsFromEntries(entries: unknown): TelegramPluginConfig | undefined {\n if (!Array.isArray(entries)) return undefined;\n const found = entries.find(\n (entry) =>\n typeof entry === 'object' &&\n entry !== null &&\n 'name' in entry &&\n ((entry as { name?: unknown | undefined }).name === '@wrongstack/telegram' ||\n (entry as { name?: unknown | undefined }).name === PLUGIN_NAME),\n ) as { name?: unknown | undefined; options?: unknown | undefined } | undefined;\n return found?.options && typeof found.options === 'object'\n ? (found.options as TelegramPluginConfig)\n : undefined;\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';\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, ToolValidationError } from '@wrongstack/core';\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, SlashCommand } from '@wrongstack/core';\nimport { expectDefined } from '@wrongstack/core';\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';\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 /** Group approvals stay denied unless both this and explicit user IDs are configured. */\n allowGroupApprovals?: boolean | undefined;\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.allowGroupApprovals !== 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.allowGroupApprovals === 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';\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// 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\nimport type { Logger } from '@wrongstack/core';\nimport type { TelegramApiMessage } from './api-client.js';\nimport type { TelegramBot, TelegramBotResponse } from './bot.js';\nimport { OutboundQueue, type OutboundEntry } from './outbound-queue.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\nexport class TelegramBotOutbound {\n readonly #queue: OutboundQueue;\n readonly #bot: TelegramBot;\n readonly #log: Logger;\n #stopped = false;\n\n constructor(opts: BotOutboundOptions) {\n this.#bot = opts.bot;\n this.#log = opts.log;\n this.#queue = new OutboundQueue({\n maxPerChat: opts.maxPerChat,\n maxConcurrency: opts.maxConcurrency,\n send: (chatId, text) =>\n this.#bot.sendMessage(chatId, text).then((res) => {\n if (!res.ok) {\n throw new Error(`Telegram outbound send returned ok=false for chat ${chatId}`);\n }\n return res;\n }),\n log: opts.log,\n });\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", "import type { Tool } from '@wrongstack/core';\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, type Logger, type Tool } from '@wrongstack/core';\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"],
5
- "mappings": "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",
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/bot-queue.ts", "../src/notification-channel.ts", "../src/tools/telegram-read.ts", "../src/tools/telegram-send.ts"],
4
+ "sourcesContent": ["import type { Config, Logger, Plugin, PluginAPI, SlashCommand } from '@wrongstack/core';\nimport { expectDefined } from '@wrongstack/core';\nimport type { TelegramIncomingMessage } from './bot.js';\nimport { TelegramBot } from './bot.js';\nimport { PLUGIN_NAME, readTelegramConfig, telegramConfigSchema } 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';\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}\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/** Read the Telegram section from a full Config object. */\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} {\n const ext =\n (cfg.extensions as Record<string, Record<string, unknown>> | undefined)?.[PLUGIN_NAME] ?? {};\n return {\n notifyChatId: ext.notifyChatId !== undefined ? String(ext.notifyChatId) : undefined,\n allowedOutboundChats: Array.isArray(ext.allowedOutboundChats)\n ? ext.allowedOutboundChats.filter(\n (chatId): chatId is string | number =>\n typeof chatId === 'string' || typeof chatId === 'number',\n )\n : [],\n allowedUserIds: Array.isArray(ext.allowedUsers)\n ? ext.allowedUsers.filter(\n (userId): userId is string | number =>\n typeof userId === 'string' || typeof userId === 'number',\n )\n : [],\n allowGroupApprovals: ext.allowGroupApprovals === true,\n notifyOnSessionEnd: ext.notifyOnSessionEnd === true,\n notifyOnDelegate: ext.notifyOnDelegate !== false, // default true\n longToolThresholdMs:\n typeof ext.longToolThresholdMs === 'number' ? ext.longToolThresholdMs : 30_000,\n maxMessageLength: typeof ext.maxMessageLength === 'number' ? ext.maxMessageLength : 4000,\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 configSchema: telegramConfigSchema,\n defaultConfig: {\n allowedOutboundChats: [],\n pollIntervalSec: 2,\n notifyOnSessionEnd: false,\n longToolThresholdMs: 30_000,\n maxMessageLength: 4000,\n },\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 rawCfg = cfg as ReturnType<typeof readTelegramConfig> & {\n allowGroupApprovals?: boolean | undefined;\n };\n const runtimeCfg: RuntimeConfig = {\n notifyChatId: cfg.notifyChatId,\n allowedOutboundChats: [...(cfg.allowedOutboundChats ?? [])],\n allowedUserIds: [...(cfg.allowedUsers ?? [])],\n allowGroupApprovals: rawCfg.allowGroupApprovals === true,\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 };\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 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 // 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 const outbound = new TelegramBotOutbound({\n bot,\n log,\n maxPerChat: runtimeCfg.outboundQueuePerChat,\n maxConcurrency: runtimeCfg.outboundQueueConcurrency,\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 allowGroupApprovals: 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 ----\n // api.config is frozen at setup, but onConfigChange fires whenever the\n // ConfigStore is updated (from CLI /settings, WebUI prefSync, /telegram-settings).\n // Update the mutable runtime refs so all handlers pick up the new values\n // on the next event \u2014 no restart needed.\n const unlistenConfig = api.onConfigChange((next, prev) => {\n const fresh = telegramFromConfig(next);\n const was = telegramFromConfig(prev);\n runtimeCfg.notifyChatId = fresh.notifyChatId;\n runtimeCfg.allowedOutboundChats = fresh.allowedOutboundChats;\n runtimeCfg.allowedUserIds = fresh.allowedUserIds;\n runtimeCfg.allowGroupApprovals = fresh.allowGroupApprovals;\n runtimeCfg.notifyOnSessionEnd = fresh.notifyOnSessionEnd;\n runtimeCfg.notifyOnDelegate = fresh.notifyOnDelegate;\n runtimeCfg.longToolThresholdMs = fresh.longToolThresholdMs;\n runtimeCfg.maxMessageLength = fresh.maxMessageLength;\n // When notifyChatId or maxMessageLength changes, rebuild the\n // notification channel so it always reflects the live config.\n if (fresh.notifyChatId !== was.notifyChatId || fresh.maxMessageLength !== was.maxMessageLength) {\n notifyChannel =\n fresh.notifyChatId !== undefined\n ? new TelegramNotificationChannel({\n bot,\n chatId: fresh.notifyChatId,\n maxMessageLength: fresh.maxMessageLength,\n enqueueNotification: (chatId, text) =>\n outbound.enqueueNotification(chatId, text),\n log,\n })\n : undefined;\n }\n log.debug('Telegram notification settings updated from config', {\n notifyOnSessionEnd: runtimeCfg.notifyOnSessionEnd,\n notifyOnDelegate: runtimeCfg.notifyOnDelegate,\n longToolThresholdMs: runtimeCfg.longToolThresholdMs,\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}\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 return this.request<TelegramApiMessage>('sendMessage', {\n body: {\n chat_id: String(chatId),\n text,\n disable_web_page_preview: true,\n },\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 return this.request<TelegramApiMessage>('sendMessage', {\n body: {\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 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';\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 /** 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 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 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 });\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 });\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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n", "import type { PluginAPI } from '@wrongstack/core';\n\nexport const PLUGIN_NAME = 'telegram';\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}\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};\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 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 },\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 config = api.config as never as Record<string, unknown>;\n const extensions = config.extensions as Record<string, unknown> | undefined;\n const pluginEntries = config.plugins;\n const legacyPlugins = pluginEntries as Record<string, unknown> | undefined;\n const legacyOpts =\n legacyPlugins && !Array.isArray(legacyPlugins) ? legacyPlugins[PLUGIN_NAME] : undefined;\n const entryOpts = pluginOptionsFromEntries(pluginEntries);\n const extensionOpts = extensions?.[PLUGIN_NAME];\n const opts = {\n ...((legacyOpts ?? entryOpts) as TelegramPluginConfig),\n ...((extensionOpts ?? {}) as TelegramPluginConfig),\n };\n const inboundMode = resolveInboundMode(opts, {\n configured: legacyOpts !== undefined || entryOpts !== undefined || extensionOpts !== undefined,\n warn: api.log?.warn.bind(api.log),\n });\n\n return {\n ...DEFAULT_CONFIG,\n ...opts,\n inboundMode,\n };\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\nfunction pluginOptionsFromEntries(entries: unknown): TelegramPluginConfig | undefined {\n if (!Array.isArray(entries)) return undefined;\n const found = entries.find(\n (entry) =>\n typeof entry === 'object' &&\n entry !== null &&\n 'name' in entry &&\n ((entry as { name?: unknown | undefined }).name === '@wrongstack/telegram' ||\n (entry as { name?: unknown | undefined }).name === PLUGIN_NAME),\n ) as { name?: unknown | undefined; options?: unknown | undefined } | undefined;\n return found?.options && typeof found.options === 'object'\n ? (found.options as TelegramPluginConfig)\n : undefined;\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';\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, ToolValidationError } from '@wrongstack/core';\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, SlashCommand } from '@wrongstack/core';\nimport { expectDefined } from '@wrongstack/core';\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';\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 /** Group approvals stay denied unless both this and explicit user IDs are configured. */\n allowGroupApprovals?: boolean | undefined;\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.allowGroupApprovals !== 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.allowGroupApprovals === 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';\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// 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\nimport type { Logger } from '@wrongstack/core';\nimport type { TelegramApiMessage } from './api-client.js';\nimport type { TelegramBot, TelegramBotResponse } from './bot.js';\nimport { OutboundQueue, type OutboundEntry } from './outbound-queue.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\nexport class TelegramBotOutbound {\n readonly #queue: OutboundQueue;\n readonly #bot: TelegramBot;\n readonly #log: Logger;\n #stopped = false;\n\n constructor(opts: BotOutboundOptions) {\n this.#bot = opts.bot;\n this.#log = opts.log;\n this.#queue = new OutboundQueue({\n maxPerChat: opts.maxPerChat,\n maxConcurrency: opts.maxConcurrency,\n send: (chatId, text) =>\n this.#bot.sendMessage(chatId, text).then((res) => {\n if (!res.ok) {\n throw new Error(`Telegram outbound send returned ok=false for chat ${chatId}`);\n }\n return res;\n }),\n log: opts.log,\n });\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';\nimport type {\n NotificationChannel,\n NotificationLevel,\n NotificationMessage,\n NotificationResult,\n} from '@wrongstack/core';\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';\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, type Logger, type Tool } from '@wrongstack/core';\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"],
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
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * TelegramNotificationChannel — NotificationChannel implementation for
3
+ * one-way Telegram message delivery.
4
+ *
5
+ * This is the **notification-only** path: fire-and-forget messages sent to
6
+ * a configured chat when the Notifier routes a `NotificationMessage` to
7
+ * the `"telegram"` channel. It wraps `bot.sendMessage()` with the standard
8
+ * scrubbing and truncation pipeline used by every outgoing Telegram message.
9
+ *
10
+ * This channel does NOT handle:
11
+ * - 2-way communication (telegram_read, telegram_approve, inline keyboards)
12
+ * - Manual sends via the telegram_send tool (those go through
13
+ * `TelegramBotOutbound.sendManual()` for queue ordering + error surfacing)
14
+ * - Slash commands or polling
15
+ *
16
+ * Those remain in the main `@wrongstack/telegram` plugin, which continues
17
+ * to own the TelegramBot instance, the inbound poller, the outbound queue
18
+ * for manual sends, and the system prompt contributor.
19
+ *
20
+ * @module telegram
21
+ * @public
22
+ */
23
+ import type { Logger } from '@wrongstack/core';
24
+ import type { NotificationChannel, NotificationMessage, NotificationResult } from '@wrongstack/core';
25
+ import type { TelegramBot } from './bot.js';
26
+ export interface TelegramNotificationChannelOptions {
27
+ /** The TelegramBot instance (owned by the main plugin). */
28
+ readonly bot: TelegramBot;
29
+ /** Queue-backed notification sender used by the plugin runtime. */
30
+ readonly enqueueNotification?: ((chatId: string | number, text: string) => void) | undefined;
31
+ /** Target chat or user ID for all notifications sent through this channel. */
32
+ readonly chatId: string | number;
33
+ /**
34
+ * Maximum message length in characters. Default 4000 (Telegram's hard
35
+ * cap is 4096; `truncateForTelegram` clamps internally). */
36
+ readonly maxMessageLength?: number | undefined;
37
+ /** Logger for debug-level diagnostics. */
38
+ readonly log?: Logger | undefined;
39
+ }
40
+ export declare class TelegramNotificationChannel implements NotificationChannel {
41
+ #private;
42
+ readonly name: 'telegram';
43
+ readonly type: 'telegram';
44
+ constructor(opts: TelegramNotificationChannelOptions);
45
+ /**
46
+ * Deliver a notification message to the configured Telegram chat.
47
+ *
48
+ * Renders the `NotificationMessage` into a single Telegram text message:
49
+ * - Prepends a level-based emoji icon (ℹ️ / ⚠️ / 🚨)
50
+ * - Combines `title` (when present) and `body`
51
+ * - Runs through credential scrubbing
52
+ * - Truncates to the configured max length
53
+ *
54
+ * **Does not throw.** Transport errors are caught and returned as
55
+ * `{ ok: false, error: "…" }`.
56
+ */
57
+ deliver(msg: NotificationMessage): Promise<NotificationResult>;
58
+ /**
59
+ * Liveness probe — delegates to the bot's health check.
60
+ * Returns `{ ok: true }` when the bot token is valid and
61
+ * api.telegram.org is reachable.
62
+ */
63
+ ping(): Promise<{
64
+ ok: boolean;
65
+ error?: string | undefined;
66
+ }>;
67
+ }
68
+ //# sourceMappingURL=notification-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-channel.d.ts","sourceRoot":"","sources":["../src/notification-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EACV,mBAAmB,EAEnB,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAkB5C,MAAM,WAAW,kCAAkC;IACjD,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,CAAC,mBAAmB,CAAC,EACzB,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GACjD,SAAS,CAAC;IACd,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC;;gEAE4D;IAC5D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,0CAA0C;IAC1C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,qBAAa,2BAA4B,YAAW,mBAAmB;;IACrE,QAAQ,CAAC,IAAI,EAAG,UAAU,CAAU;IACpC,QAAQ,CAAC,IAAI,EAAG,UAAU,CAAU;IAUpC,YAAY,IAAI,EAAE,kCAAkC,EAMnD;IAED;;;;;;;;;;;OAWG;IACG,OAAO,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA8CnE;IAED;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAUjE;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/telegram",
3
- "version": "0.291.1",
3
+ "version": "0.292.0",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack plugin — Telegram bridge: send messages, receive prompts, get notified.",
6
6
  "repository": {
@@ -25,12 +25,12 @@
25
25
  "dist"
26
26
  ],
27
27
  "peerDependencies": {
28
- "@wrongstack/core": "0.291.1"
28
+ "@wrongstack/core": "0.292.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^26.1.1",
32
32
  "typescript": "^7.0.2",
33
- "@wrongstack/core": "0.291.1"
33
+ "@wrongstack/core": "0.292.0"
34
34
  },
35
35
  "publishConfig": {
36
36
  "access": "public"