@wrongstack/telegram 0.273.1 → 0.275.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.js +246 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -42,6 +42,10 @@ var TelegramBot = class _TelegramBot {
|
|
|
42
42
|
// Circular buffer for incoming messages
|
|
43
43
|
bufferMax;
|
|
44
44
|
buffer = [];
|
|
45
|
+
// Pending callback_query waiters keyed by the `data` string they
|
|
46
|
+
// registered. Cleared on stop(). Used by `telegram_approve` to bridge a
|
|
47
|
+
// synchronous-looking API onto the async callback event.
|
|
48
|
+
callbackWaiters = /* @__PURE__ */ new Map();
|
|
45
49
|
constructor(opts) {
|
|
46
50
|
this.baseUrl = `https://api.telegram.org/bot${opts.token}`;
|
|
47
51
|
this.safeBaseUrl = redactToken(this.baseUrl, opts.token);
|
|
@@ -83,6 +87,11 @@ var TelegramBot = class _TelegramBot {
|
|
|
83
87
|
clearTimeout(this.standbyTimer);
|
|
84
88
|
this.standbyTimer = null;
|
|
85
89
|
}
|
|
90
|
+
for (const [key, waiter] of this.callbackWaiters) {
|
|
91
|
+
clearTimeout(waiter.timer);
|
|
92
|
+
waiter.resolve({ approved: false, fromUser: "shutdown" });
|
|
93
|
+
this.callbackWaiters.delete(key);
|
|
94
|
+
}
|
|
86
95
|
this.lock?.release();
|
|
87
96
|
this.log.info("Telegram bot stopped");
|
|
88
97
|
}
|
|
@@ -198,12 +207,54 @@ var TelegramBot = class _TelegramBot {
|
|
|
198
207
|
throw lastErr;
|
|
199
208
|
}
|
|
200
209
|
// ------------------------------------------------------------------
|
|
210
|
+
// Outgoing — send a message with an inline keyboard
|
|
211
|
+
// ------------------------------------------------------------------
|
|
212
|
+
/**
|
|
213
|
+
* Send a message that has up to one row of inline buttons (Telegram's
|
|
214
|
+
* `inline_keyboard`). Used by `telegram_approve` to present a
|
|
215
|
+
* yes/no prompt. The keyboard payload is opaque to the bot — callers
|
|
216
|
+
* pass already-encoded `callback_data` strings (≤ 64 bytes each).
|
|
217
|
+
*/
|
|
218
|
+
async sendMessageWithKeyboard(chatId, text, buttons) {
|
|
219
|
+
const url = `${this.baseUrl}/sendMessage`;
|
|
220
|
+
const body = JSON.stringify({
|
|
221
|
+
chat_id: String(chatId),
|
|
222
|
+
text,
|
|
223
|
+
disable_web_page_preview: true,
|
|
224
|
+
reply_markup: {
|
|
225
|
+
inline_keyboard: [buttons.map((b) => ({ text: b.text, callback_data: b.callback_data }))]
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
let lastErr;
|
|
229
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
230
|
+
try {
|
|
231
|
+
const res = await fetch(url, {
|
|
232
|
+
method: "POST",
|
|
233
|
+
headers: { "Content-Type": "application/json" },
|
|
234
|
+
body,
|
|
235
|
+
signal: AbortSignal.timeout(1e4)
|
|
236
|
+
});
|
|
237
|
+
const data = await res.json();
|
|
238
|
+
if (!data.ok) {
|
|
239
|
+
throw new Error(`Telegram API error ${data.error_code}: ${data.description}`);
|
|
240
|
+
}
|
|
241
|
+
return data;
|
|
242
|
+
} catch (err) {
|
|
243
|
+
lastErr = err;
|
|
244
|
+
if (attempt < 3) await sleep(1e3);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
throw lastErr;
|
|
248
|
+
}
|
|
249
|
+
// ------------------------------------------------------------------
|
|
201
250
|
// Health
|
|
202
251
|
// ------------------------------------------------------------------
|
|
203
252
|
async health() {
|
|
253
|
+
const ctrl = new AbortController();
|
|
254
|
+
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
204
255
|
try {
|
|
205
256
|
const url = `${this.baseUrl}/getMe`;
|
|
206
|
-
const res = await fetch(url, { signal:
|
|
257
|
+
const res = await fetch(url, { signal: ctrl.signal });
|
|
207
258
|
const data = await res.json();
|
|
208
259
|
if (!data.ok || !data.result) {
|
|
209
260
|
return { ok: false, error: data.description ?? "Unknown error" };
|
|
@@ -211,6 +262,8 @@ var TelegramBot = class _TelegramBot {
|
|
|
211
262
|
return { ok: true, username: data.result.username };
|
|
212
263
|
} catch (err) {
|
|
213
264
|
return { ok: false, error: err.message };
|
|
265
|
+
} finally {
|
|
266
|
+
clearTimeout(timer);
|
|
214
267
|
}
|
|
215
268
|
}
|
|
216
269
|
// ------------------------------------------------------------------
|
|
@@ -245,6 +298,10 @@ var TelegramBot = class _TelegramBot {
|
|
|
245
298
|
const updates = data.result ?? [];
|
|
246
299
|
for (const upd of updates) {
|
|
247
300
|
this.offset = upd.update_id + 1;
|
|
301
|
+
if (upd.callback_query) {
|
|
302
|
+
void this.dispatchCallback(upd.callback_query);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
248
305
|
const raw = upd.message ?? upd.edited_message;
|
|
249
306
|
if (!raw?.text) continue;
|
|
250
307
|
const msg = { ...raw, text: raw.text };
|
|
@@ -283,6 +340,56 @@ var TelegramBot = class _TelegramBot {
|
|
|
283
340
|
while (this.buffer.length > this.bufferMax) this.buffer.shift();
|
|
284
341
|
this.onMessage(incoming);
|
|
285
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Handle an inbound `callback_query` update: route it to a registered
|
|
345
|
+
* waiter (if any), and acknowledge it via `answerCallbackQuery` so the
|
|
346
|
+
* client stops spinning. Telegram requires the answer within 10 s.
|
|
347
|
+
*/
|
|
348
|
+
async dispatchCallback(cq) {
|
|
349
|
+
const key = cq.data ?? "";
|
|
350
|
+
const waiter = key !== "" ? this.callbackWaiters.get(key) : void 0;
|
|
351
|
+
const approved = key.endsWith(":yes");
|
|
352
|
+
const fromUser = cq.from?.username ?? cq.from?.first_name ?? "unknown";
|
|
353
|
+
try {
|
|
354
|
+
await fetch(`${this.baseUrl}/answerCallbackQuery`, {
|
|
355
|
+
method: "POST",
|
|
356
|
+
headers: { "Content-Type": "application/json" },
|
|
357
|
+
body: JSON.stringify({
|
|
358
|
+
callback_query_id: cq.id,
|
|
359
|
+
text: approved ? "Approved \u2713" : "Denied \u2717",
|
|
360
|
+
show_alert: false
|
|
361
|
+
}),
|
|
362
|
+
signal: AbortSignal.timeout(5e3)
|
|
363
|
+
});
|
|
364
|
+
} catch (err) {
|
|
365
|
+
this.log.debug(`answerCallbackQuery failed: ${err.message}`);
|
|
366
|
+
}
|
|
367
|
+
if (waiter) {
|
|
368
|
+
clearTimeout(waiter.timer);
|
|
369
|
+
this.callbackWaiters.delete(key);
|
|
370
|
+
waiter.resolve({ approved, fromUser });
|
|
371
|
+
} else {
|
|
372
|
+
this.log.debug(`Unmatched callback_query data="${key}" (no pending waiter)`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Register a waiter for a callback_query whose `data` field equals `key`.
|
|
377
|
+
* Resolves with `{ approved, fromUser }` when a matching press arrives, or
|
|
378
|
+
* with `{ approved: false, fromUser: 'timeout' }` after `timeoutMs`.
|
|
379
|
+
*
|
|
380
|
+
* Callers are responsible for not registering the same key twice — a
|
|
381
|
+
* second `awaitCallback` for an in-flight key is undefined.
|
|
382
|
+
*/
|
|
383
|
+
awaitCallback(key, timeoutMs) {
|
|
384
|
+
return new Promise((resolve) => {
|
|
385
|
+
const timer = setTimeout(() => {
|
|
386
|
+
if (this.callbackWaiters.delete(key)) {
|
|
387
|
+
resolve({ approved: false, fromUser: "timeout" });
|
|
388
|
+
}
|
|
389
|
+
}, timeoutMs);
|
|
390
|
+
this.callbackWaiters.set(key, { resolve, timer });
|
|
391
|
+
});
|
|
392
|
+
}
|
|
286
393
|
async loadOffset() {
|
|
287
394
|
if (!this.offsetStoragePath) return;
|
|
288
395
|
try {
|
|
@@ -412,6 +519,49 @@ function pluginOptionsFromEntries(entries) {
|
|
|
412
519
|
return found?.options && typeof found.options === "object" ? found.options : void 0;
|
|
413
520
|
}
|
|
414
521
|
|
|
522
|
+
// src/redact.ts
|
|
523
|
+
var SENSITIVE_FLAG_PATTERNS = [
|
|
524
|
+
// --flag=value or --flag "value" (value captured up to next space/comma)
|
|
525
|
+
/--(?: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,
|
|
526
|
+
// Short flags: -t value, -p value. Only the SEPARATED form (`-t value`,
|
|
527
|
+
// `-t=value`) is matched — the glued form (`-tvalue`) is intentionally
|
|
528
|
+
// NOT matched because it produces too many false positives in practice
|
|
529
|
+
// (`-target`, `-tries`, `-timeout` all start with `-t`). A user typing
|
|
530
|
+
// `curl -tSECRET` is extremely rare; a user typing `clang -target=...`
|
|
531
|
+
// is daily. The lookbehind `(?<![-\w])` rejects `-t` inside `--token`
|
|
532
|
+
// where the preceding char is another `-`.
|
|
533
|
+
/(?<![-\w])-t(?:[\s=][^\s,]+)/,
|
|
534
|
+
/(?<![-\w])-(?:p|password)(?:[\s=][^\s,]+)/gi,
|
|
535
|
+
// env-var style: TOKEN=x, API_KEY=y, DATABASE_URL=z, …
|
|
536
|
+
/(?: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,
|
|
537
|
+
// Generic high-entropy look — only when preceded by a flag name.
|
|
538
|
+
/--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/
|
|
539
|
+
];
|
|
540
|
+
function redactSecrets(text) {
|
|
541
|
+
let result = text;
|
|
542
|
+
for (const pattern of SENSITIVE_FLAG_PATTERNS) {
|
|
543
|
+
result = result.replace(pattern, (match) => {
|
|
544
|
+
const eq = match.indexOf("=");
|
|
545
|
+
const sp = match.search(/\s/);
|
|
546
|
+
let delim = null;
|
|
547
|
+
let delimIdx = -1;
|
|
548
|
+
if (eq !== -1) {
|
|
549
|
+
delim = "=";
|
|
550
|
+
delimIdx = eq;
|
|
551
|
+
} else if (sp !== -1) {
|
|
552
|
+
delim = match[sp] ?? null;
|
|
553
|
+
delimIdx = sp;
|
|
554
|
+
}
|
|
555
|
+
if (delim !== null && delimIdx >= 0) {
|
|
556
|
+
const flag = match.slice(0, delimIdx + 1);
|
|
557
|
+
return `${flag}[REDACTED]`;
|
|
558
|
+
}
|
|
559
|
+
return "**redacted**";
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
|
|
415
565
|
// src/format.ts
|
|
416
566
|
function fmtDuration(ms) {
|
|
417
567
|
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
@@ -423,7 +573,8 @@ function fmtTokens(n) {
|
|
|
423
573
|
}
|
|
424
574
|
function fmtToolOutput(raw) {
|
|
425
575
|
if (!raw) return "(no output)";
|
|
426
|
-
const
|
|
576
|
+
const redacted = redactSecrets(raw);
|
|
577
|
+
const cleaned = redacted.replace(/^[{[]\s*/, "").replace(/\s*[}\]]$/, "").replace(/"([^"]+)":/g, "$1: ").replace(/\\n/g, "\n").replace(/\\"/g, '"').trim() || redacted;
|
|
427
578
|
const lines = cleaned.split("\n").filter((l) => l.trim().length > 0);
|
|
428
579
|
let preview = lines.slice(0, 3).join("\n");
|
|
429
580
|
if (lines.length > 3) preview += `
|
|
@@ -435,7 +586,8 @@ function formatDelegateCompleted(e) {
|
|
|
435
586
|
const icon = e.ok ? "\u2705" : "\u274C";
|
|
436
587
|
const status = e.status ?? (e.ok ? "success" : "failed");
|
|
437
588
|
const task = e.task.length > 160 ? `${e.task.slice(0, 159)}\u2026` : e.task;
|
|
438
|
-
const
|
|
589
|
+
const rawBody = e.summary?.trim() || `(no summary) \u2014 ${task}`;
|
|
590
|
+
const body = redactSecrets(rawBody);
|
|
439
591
|
const stats = [
|
|
440
592
|
`\u23F1 ${fmtDuration(e.durationMs)}`,
|
|
441
593
|
`${e.iterations} iter`,
|
|
@@ -784,6 +936,82 @@ function makeTelegramSendTool(opts) {
|
|
|
784
936
|
}
|
|
785
937
|
};
|
|
786
938
|
}
|
|
939
|
+
function makeTelegramApproveTool(opts) {
|
|
940
|
+
return {
|
|
941
|
+
name: "telegram_approve",
|
|
942
|
+
description: "Post a yes/no approval prompt to a Telegram chat with inline keyboard buttons, and wait for the user to tap one. Returns { approved, from } where approved=false on timeout or explicit deny. Use this when you need explicit human confirmation before proceeding (destructive ops, irreversible deploys, ambiguous choices).",
|
|
943
|
+
usageHint: 'telegram_approve(prompt: "Delete build artifacts?", details: "Frees 2.3 GB. Cannot be undone.", timeout_ms: 60000)',
|
|
944
|
+
category: "Telegram",
|
|
945
|
+
inputSchema: {
|
|
946
|
+
type: "object",
|
|
947
|
+
properties: {
|
|
948
|
+
prompt: {
|
|
949
|
+
type: "string",
|
|
950
|
+
maxLength: 200,
|
|
951
|
+
description: "Short label for what is being approved. Shown as the prompt heading."
|
|
952
|
+
},
|
|
953
|
+
details: {
|
|
954
|
+
type: "string",
|
|
955
|
+
maxLength: 1e3,
|
|
956
|
+
description: "Optional context under the heading."
|
|
957
|
+
},
|
|
958
|
+
chat_id: {
|
|
959
|
+
oneOf: [{ type: "string" }, { type: "integer" }],
|
|
960
|
+
description: "Chat to post the prompt to. Uses the plugin default when omitted."
|
|
961
|
+
},
|
|
962
|
+
timeout_ms: {
|
|
963
|
+
type: "integer",
|
|
964
|
+
minimum: 1e3,
|
|
965
|
+
maximum: 6e5,
|
|
966
|
+
description: "How long to wait before auto-denying. Default 60 000 ms, max 600 000 ms (10 min)."
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
required: ["prompt"]
|
|
970
|
+
},
|
|
971
|
+
permission: "auto",
|
|
972
|
+
mutating: false,
|
|
973
|
+
timeoutMs: 61e4,
|
|
974
|
+
async execute(input, _ctx, _toolOpts) {
|
|
975
|
+
const chatId = input.chat_id ?? opts.getDefaultChatId();
|
|
976
|
+
if (!chatId) {
|
|
977
|
+
throw new Error(
|
|
978
|
+
"No chat_id provided and no default notifyChatId configured. Set notifyChatId in plugin config or pass chat_id."
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
const timeoutMs = Math.min(Math.max(input.timeout_ms ?? 6e4, 1e3), 6e5);
|
|
982
|
+
const token = randomUUID().slice(0, 16);
|
|
983
|
+
const yesKey = `approve:${token}:yes`;
|
|
984
|
+
const noKey = `approve:${token}:no`;
|
|
985
|
+
const heading = `\u26A0\uFE0F ${input.prompt}`;
|
|
986
|
+
const detailsLine = input.details ? `
|
|
987
|
+
|
|
988
|
+
${truncateForTelegram(input.details, 800)}` : "";
|
|
989
|
+
const text = `${heading}${detailsLine}
|
|
990
|
+
|
|
991
|
+
_Reply by tapping a button. Auto-denies in ${Math.round(timeoutMs / 1e3)}s._`;
|
|
992
|
+
opts.log.info(`telegram_approve \u2192 chat_id=${chatId} prompt="${input.prompt.slice(0, 80)}" token=${token}`);
|
|
993
|
+
let promptMessageId;
|
|
994
|
+
try {
|
|
995
|
+
const sent = await opts.bot.sendMessageWithKeyboard(chatId, text, [
|
|
996
|
+
{ text: "\u2705 Approve", callback_data: yesKey },
|
|
997
|
+
{ text: "\u274C Deny", callback_data: noKey }
|
|
998
|
+
]);
|
|
999
|
+
promptMessageId = sent.result?.message_id;
|
|
1000
|
+
} catch (err) {
|
|
1001
|
+
opts.log.debug(`telegram_approve send failed: ${err.message}`);
|
|
1002
|
+
}
|
|
1003
|
+
const result = await Promise.race([
|
|
1004
|
+
opts.bot.awaitCallback(yesKey, timeoutMs),
|
|
1005
|
+
opts.bot.awaitCallback(noKey, timeoutMs)
|
|
1006
|
+
]);
|
|
1007
|
+
return {
|
|
1008
|
+
approved: result.approved,
|
|
1009
|
+
from: result.fromUser,
|
|
1010
|
+
prompt_message_id: promptMessageId
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
787
1015
|
|
|
788
1016
|
// src/index.ts
|
|
789
1017
|
var teardownState = null;
|
|
@@ -849,8 +1077,15 @@ var plugin = {
|
|
|
849
1077
|
log
|
|
850
1078
|
});
|
|
851
1079
|
const readTool = makeTelegramReadTool({ bot });
|
|
1080
|
+
const approveTool = makeTelegramApproveTool({
|
|
1081
|
+
bot,
|
|
1082
|
+
getDefaultChatId: () => runtimeCfg.notifyChatId,
|
|
1083
|
+
maxMessageLength: runtimeCfg.maxMessageLength,
|
|
1084
|
+
log
|
|
1085
|
+
});
|
|
852
1086
|
api.tools.register(sendTool);
|
|
853
1087
|
api.tools.register(readTool);
|
|
1088
|
+
api.tools.register(approveTool);
|
|
854
1089
|
const offs = [];
|
|
855
1090
|
const unregisterPrompt = api.registerSystemPromptContributor(async () => {
|
|
856
1091
|
const msgs = bot.getMessages({ limit: 5 });
|
|
@@ -941,10 +1176,17 @@ var plugin = {
|
|
|
941
1176
|
});
|
|
942
1177
|
});
|
|
943
1178
|
offs.push(unlistenConfig);
|
|
1179
|
+
const probe = await bot.health();
|
|
1180
|
+
if (!probe.ok) {
|
|
1181
|
+
throw new Error(
|
|
1182
|
+
`Telegram plugin startup failed: ${probe.error ?? "unknown error"}. Verify botToken in extensions.telegram (token from @BotFather, format "<id>:<35+ chars>").`
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
log.info(`Telegram self-test ok: @${probe.username ?? "unknown"} (api.telegram.org reachable)`);
|
|
944
1186
|
bot.start();
|
|
945
1187
|
teardownState = {
|
|
946
1188
|
offs,
|
|
947
|
-
toolNames: [sendTool.name, readTool.name],
|
|
1189
|
+
toolNames: [sendTool.name, readTool.name, approveTool.name],
|
|
948
1190
|
commandNames,
|
|
949
1191
|
bot,
|
|
950
1192
|
runtimeCfg
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bot.ts","../src/config.ts","../src/format.ts","../src/poll-lock.ts","../src/slash-commands/index.ts","../src/tools/telegram-read.ts","../src/tools/telegram-send.ts","../src/index.ts"],"names":["readFileSync","writeFileSync","expectDefined"],"mappings":"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","file":"index.js","sourcesContent":["import type { Logger } from '@wrongstack/core';\nimport { sleep } from '@wrongstack/core/utils';\nimport type { PollLock } from './poll-lock.js';\n\n// ---------------------------------------------------------------------------\n// Redaction helpers\n// ---------------------------------------------------------------------------\n/** Redact the bot token from a URL for safe logging. */\nfunction redactToken(url: string, token: string): string {\n return url.replace(token, '[REDACTED]');\n}\n\n// ---------------------------------------------------------------------------\n// Telegram Bot API types (subset used by this plugin)\n// ---------------------------------------------------------------------------\n\ninterface TgUser {\n id: number;\n is_bot: boolean;\n first_name: string;\n username?: string | undefined;\n}\n\ninterface TgChat {\n id: number;\n type: 'private' | 'group' | 'supergroup' | 'channel';\n title?: string | undefined;\n username?: string | undefined;\n}\n\ninterface TgMessage {\n message_id: number;\n from?: TgUser | undefined;\n chat: TgChat;\n date: number;\n text?: string | undefined;\n}\n\ninterface TgUpdate {\n update_id: number;\n message?: TgMessage | undefined;\n edited_message?: TgMessage | undefined;\n}\n\ninterface TgResponse<T> {\n ok: boolean;\n result?: T | undefined;\n description?: string | undefined;\n error_code?: number | undefined;\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\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 path to a file that stores the polling offset. When provided,\n * the offset is persisted on every successful poll and restored on startup,\n * preventing message replay after crashes or restarts.\n */\n offsetStoragePath?: string | 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 baseUrl: string;\n /** Base URL with token redacted, safe to use in log calls. */\n private readonly safeBaseUrl: string;\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 /** If set, the offset is persisted here after each successful poll. */\n private readonly offsetStoragePath?: string | 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 constructor(opts: TelegramBotOptions) {\n this.baseUrl = `https://api.telegram.org/bot${opts.token}`;\n this.safeBaseUrl = redactToken(this.baseUrl, 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.offsetStoragePath = opts.offsetStoragePath;\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.offsetStoragePath) {\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 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 — 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 — taking over polling.');\n } else {\n this.log.info(`Telegram bot polling started (${this.safeBaseUrl})`);\n }\n this.schedulePoll();\n }\n\n /** The lock was stolen while we held it — 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('Telegram: poll lock lost to another instance — pausing polling and standing by.');\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 — incoming messages the agent can read\n // ------------------------------------------------------------------\n\n /** Return buffered messages, newest first. Optionally filter by chat. */\n getMessages(opts?: { chatId?: string | number | undefined; limit?: number | undefined }): 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 — send a message\n // ------------------------------------------------------------------\n\n async sendMessage(chatId: string | number, text: string): Promise<TgResponse<TgMessage>> {\n const url = `${this.baseUrl}/sendMessage`;\n const body = JSON.stringify({\n chat_id: String(chatId),\n text,\n disable_web_page_preview: true,\n });\n\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 res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n signal: AbortSignal.timeout(10_000),\n });\n const data = (await res.json()) as TgResponse<TgMessage>;\n if (!data.ok) {\n throw new Error(`Telegram API error ${data.error_code}: ${data.description}`);\n }\n return data;\n } catch (err) {\n lastErr = err;\n if (attempt < 3) {\n this.log.debug(`Telegram sendMessage attempt ${attempt} failed, retrying in 1s...`);\n await sleep(1000);\n }\n }\n }\n throw lastErr;\n }\n\n // ------------------------------------------------------------------\n // Health\n // ------------------------------------------------------------------\n\n async health(): Promise<{ ok: boolean; username?: string | undefined; error?: string | undefined }> {\n try {\n const url = `${this.baseUrl}/getMe`;\n const res = await fetch(url, { signal: AbortSignal.timeout(5000) });\n const data = (await res.json()) as TgResponse<TgUser>;\n if (!data.ok || !data.result) {\n return { ok: false, error: data.description ?? 'Unknown error' };\n }\n return { ok: true, username: data.result.username };\n } catch (err) {\n return { ok: false, error: (err as Error).message };\n }\n }\n\n // ------------------------------------------------------------------\n // Polling\n // ------------------------------------------------------------------\n\n private schedulePoll(): void {\n if (!this.pollActive) return;\n // Lost the poll lock mid-flight — 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 url = `${this.baseUrl}/getUpdates?offset=${this.offset}&timeout=10`;\n const res = await fetch(url, { signal: this.controller.signal });\n const data = (await res.json()) as TgResponse<TgUpdate[]>;\n\n if (!data.ok) {\n if (data.error_code === 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) — 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) — backing off to 60s polls until it stops.',\n );\n }\n }\n this.log.debug(`Telegram getUpdates failed: ${data.description}`);\n return;\n }\n this.conflictStreak = 0;\n\n const updates = data.result ?? [];\n for (const upd of updates) {\n this.offset = upd.update_id + 1;\n const raw = upd.message ?? upd.edited_message;\n if (!raw?.text) continue;\n const msg = { ...raw, text: raw.text };\n this.processMessage(msg);\n }\n\n // Persist offset after each successful poll to prevent message replay\n // after crashes or restarts.\n if (this.offsetStoragePath && this.offset > 0) {\n void this.saveOffset();\n }\n } catch (err) {\n if ((err as Error).name === 'AbortError') return;\n this.log.debug(`Telegram poll error: ${(err as Error).message}`);\n }\n }\n\n private processMessage(msg: TgMessage & { text: string }): void {\n const chatId = String(msg.chat.id);\n const userId = msg.from ? String(msg.from.id) : undefined;\n\n // Allowlist checks\n if (this.allowedUsers.size > 0 && userId && !this.allowedUsers.has(userId)) {\n this.log.debug(`Ignoring message from user ${userId} (not in allowedUsers)`);\n void this.sendMessage(chatId, '⛔ You are not authorized to interact with this bot.');\n return;\n }\n if (this.allowedChats.size > 0 && !this.allowedChats.has(chatId)) {\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 private async loadOffset(): Promise<void> {\n if (!this.offsetStoragePath) return;\n try {\n const { readFileSync } = await import('node:fs');\n const raw = readFileSync(this.offsetStoragePath, 'utf8').trim();\n const n = Number.parseInt(raw, 10);\n if (Number.isFinite(n) && n >= 0) {\n this.offset = n;\n this.log.debug(`Telegram polling offset restored: ${this.offset}`);\n }\n } catch {\n // File doesn't exist yet — start from 0, which is correct.\n }\n }\n\n private async saveOffset(): Promise<void> {\n if (!this.offsetStoragePath) return;\n try {\n const { writeFileSync } = await import('node:fs');\n // Write atomically so a crash mid-write can't leave a corrupt file.\n writeFileSync(this.offsetStoragePath, String(this.offset), 'utf8');\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 \"…\" to signal intentional truncation.\n */\nexport function truncateForTelegram(text: string, maxLen = 4000): string {\n if (text.length <= maxLen) return text;\n\n // Reserve room for truncation suffix\n const cutoff = maxLen - 30;\n if (cutoff <= 0) return `${text.slice(0, maxLen - 1)}…`;\n\n const searchEnd = Math.min(text.length, maxLen);\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…`;\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…`;\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)}…`;\n }\n\n // 4. Word boundary (space)\n const spaceIdx = text.lastIndexOf(' ', searchEnd);\n if (spaceIdx > cutoff) {\n return `${text.slice(0, spaceIdx)} …`;\n }\n\n // 5. Hard cut\n return `${text.slice(0, maxLen - 20)}…[+${text.length - maxLen + 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\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n}\n","import type { PluginAPI } from '@wrongstack/core';\r\n\r\nexport const PLUGIN_NAME = 'telegram';\r\n\r\nexport interface TelegramPluginConfig {\r\n /** Telegram Bot API token (from @BotFather). */\r\n botToken: string;\r\n /**\r\n * Default chat ID for outgoing notifications.\r\n * The agent's `telegram_send` tool can override per-call.\r\n */\r\n notifyChatId?: string | number | undefined;\r\n /**\r\n * List of user/chat IDs allowed to interact with the bot.\r\n * Empty = allow all. Recommended to set in production.\r\n */\r\n allowedUsers?: Array<string | number> | undefined;\r\n /**\r\n * List of group/chat IDs the bot is allowed to read from.\r\n * Empty = allow all. Narrow this to prevent noise.\r\n */\r\n allowedChats?: Array<string | number> | undefined;\r\n /** Polling interval in seconds (default: 2). */\r\n pollIntervalSec?: number | undefined;\r\n /** Notify on Telegram when a session ends. */\r\n notifyOnSessionEnd?: boolean | undefined;\r\n /** Notify when a tool runs longer than this threshold (ms). Set 0 to disable. */\r\n longToolThresholdMs?: number | undefined;\r\n /** Notify (humanized) when a `delegate` subagent finishes. Default: true. */\r\n notifyOnDelegate?: boolean | undefined;\r\n /** Maximum message length for Telegram (Telegram caps at 4096). */\r\n maxMessageLength?: number | undefined;\r\n /**\r\n * Path to a file that stores the Telegram polling offset. When set,\r\n * the offset is persisted on every successful poll and restored on startup,\r\n * preventing message replay after crashes or restarts.\r\n * The directory must already exist and be writable.\r\n */\r\n offsetStoragePath?: string | undefined;\r\n /**\r\n * Elect a single poller per bot token across wstack instances (default:\r\n * true). Telegram allows one `getUpdates` consumer per token; without this,\r\n * two instances sharing a token fight and get HTTP 409 on every poll.\r\n * Extra instances stand by and take over when the active poller stops.\r\n * Set false only if this is guaranteed to be the sole consumer.\r\n */\r\n singleInstanceLock?: boolean | undefined;\r\n}\r\n\r\nexport const DEFAULT_CONFIG: Required<Omit<TelegramPluginConfig, 'botToken' | 'notifyChatId' | 'offsetStoragePath'>> = {\r\n allowedUsers: [],\r\n allowedChats: [],\r\n pollIntervalSec: 2,\r\n notifyOnSessionEnd: false,\r\n longToolThresholdMs: 30_000,\r\n notifyOnDelegate: true,\r\n maxMessageLength: 4000,\r\n singleInstanceLock: true,\r\n};\r\n\r\nexport const telegramConfigSchema = {\r\n type: 'object',\r\n properties: {\r\n botToken: { type: 'string', description: 'Telegram Bot API token from @BotFather' },\r\n notifyChatId: {\r\n oneOf: [{ type: 'string' }, { type: 'integer' }],\r\n description: 'Default chat ID for outgoing notifications',\r\n },\r\n allowedUsers: {\r\n type: 'array',\r\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\r\n description: 'User IDs allowed to interact with the bot',\r\n },\r\n allowedChats: {\r\n type: 'array',\r\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\r\n description: 'Chat IDs the bot is allowed to read from',\r\n },\r\n pollIntervalSec: {\r\n type: 'integer',\r\n minimum: 1,\r\n maximum: 60,\r\n description: 'Polling interval in seconds',\r\n },\r\n notifyOnSessionEnd: { type: 'boolean' },\r\n longToolThresholdMs: { type: 'integer', minimum: 0 },\r\n notifyOnDelegate: { type: 'boolean' },\r\n maxMessageLength: { type: 'integer', minimum: 100, maximum: 4096 },\r\n singleInstanceLock: {\r\n type: 'boolean',\r\n description: 'Elect a single getUpdates poller per bot token across wstack instances (default true)',\r\n },\r\n },\r\n required: ['botToken'],\r\n};\r\n\r\nexport function readTelegramConfig(\r\n api: Pick<PluginAPI, 'config'>,\r\n): Required<Omit<TelegramPluginConfig, 'notifyChatId' | 'offsetStoragePath'>> &\r\n Pick<TelegramPluginConfig, 'notifyChatId' | 'offsetStoragePath'> {\r\n const config = api.config as never as Record<string, unknown>;\r\n const extensions = config.extensions as Record<string, unknown> | undefined;\r\n const pluginEntries = config.plugins;\r\n const legacyPlugins = pluginEntries as Record<string, unknown> | undefined;\r\n const legacyOpts =\r\n legacyPlugins && !Array.isArray(legacyPlugins) ? legacyPlugins[PLUGIN_NAME] : undefined;\r\n const entryOpts = pluginOptionsFromEntries(pluginEntries);\r\n const opts = {\r\n ...((legacyOpts ?? entryOpts) as TelegramPluginConfig),\r\n ...((extensions?.[PLUGIN_NAME] ?? {}) as TelegramPluginConfig),\r\n };\r\n return {\r\n ...DEFAULT_CONFIG,\r\n ...opts,\r\n };\r\n}\r\n\r\nfunction pluginOptionsFromEntries(entries: unknown): TelegramPluginConfig | undefined {\r\n if (!Array.isArray(entries)) return undefined;\r\n const found = entries.find(\r\n (entry) =>\r\n typeof entry === 'object' &&\r\n entry !== null &&\r\n 'name' in entry &&\r\n ((entry as { name?: unknown | undefined }).name === '@wrongstack/telegram' ||\r\n (entry as { name?: unknown | undefined }).name === PLUGIN_NAME),\r\n ) as { name?: unknown | undefined; options?: unknown | undefined } | undefined;\r\n return found?.options && typeof found.options === 'object'\r\n ? (found.options as TelegramPluginConfig)\r\n : undefined;\r\n}\r\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 — status markers only, no decoration.\n// ---------------------------------------------------------------------------\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 — 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, limits to ~300 chars, preserves first/last lines.\n */\nexport function fmtToolOutput(raw: string | undefined): string {\n if (!raw) return '(no output)';\n const cleaned = raw\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 || raw;\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… +${lines.length - 3} more lines`;\n if (preview.length > 300) preview = `${preview.slice(0, 297)}…`;\n return preview;\n}\n\n// ---------------------------------------------------------------------------\n// Event → message formatters\n// ---------------------------------------------------------------------------\n\n/**\n * Render a finished delegation as a readable Telegram message.\n *\n * Example:\n * ✅ Delegate → bug-hunter · success\n * Found 3 null-deref risks in auth.ts and patched the worst one…\n * ⏱ 3m · 4 iter · 37 tools · 💲0.0820\n */\nexport function formatDelegateCompleted(e: DelegateCompletedLike): string {\n const icon = e.ok ? '✅' : '❌';\n const status = e.status ?? (e.ok ? 'success' : 'failed');\n const task = e.task.length > 160 ? `${e.task.slice(0, 159)}…` : e.task;\n\n // Prefer the host's one-line summary; fall back to echoing the task when a\n // failure produced no summary.\n const body = e.summary?.trim() || `(no summary) — ${task}`;\n\n const stats = [\n `⏱ ${fmtDuration(e.durationMs)}`,\n `${e.iterations} iter`,\n `${e.toolCalls} tools`,\n ];\n if (typeof e.costUsd === 'number' && e.costUsd > 0) {\n stats.push(`💲${e.costUsd.toFixed(4)}`);\n }\n\n return [`${icon} Delegate → ${e.target} · ${status}`, body, stats.join(' · ')].join('\\n');\n}\n\n/**\n * Render a long-running tool execution notification.\n *\n * Example:\n * ✅ bash completed in 45.2s\n * pnpm test — 12 suites, 47 tests passed\n * …\n */\nexport function formatToolExecuted(e: ToolExecutedLike): string {\n const icon = e.ok ? '✅' : '❌';\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 * 🏁 Session sess_abcd ended\n * ⬇ 8,234 in · ⬆ 3,456 out · 11,690 total\n * Cache: 1,200 read · 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 `🏁 Session ${id} ended`,\n `⬇ ${fmtTokens(e.inputTokens)} in · ⬆ ${fmtTokens(e.outputTokens)} out · ${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(`📦 ${parts.join(' · ')}`);\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 — `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 — 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 — 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 { expectDefined } from '@wrongstack/core';\nimport type { PluginAPI, SlashCommand } from '@wrongstack/core';\r\nimport type { TelegramBot } from '../bot.js';\r\nimport type { TelegramPluginConfig } from '../config.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// /telegram:status\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function tgStatusCommand(bot: TelegramBot, cfg: TelegramPluginConfig): SlashCommand {\r\n return {\r\n name: 'status',\r\n aliases: ['tgstat', 'tgs'],\r\n description: 'Show Telegram bot connection status and config',\r\n help: `Usage: /telegram:status\r\n\r\nShows whether the bot is connected, its username, polling interval,\r\nallowlist status, and notification settings.`,\r\n async run(_args, _ctx) {\r\n const health = await bot.health();\r\n const lines = [\r\n '═══ Telegram Plugin Status ═══',\r\n '',\r\n `Bot: ${health.ok ? `✅ @${health.username ?? 'connected'}` : `❌ ${health.error ?? 'offline'}`}`,\r\n `Running: ${bot.running ? 'yes' : 'no'}`,\r\n `Started: ${bot.startedAt ? new Date(bot.startedAt).toLocaleTimeString() : 'N/A'}`,\r\n `Poll: every ${cfg.pollIntervalSec ?? 2}s`,\r\n `Allowed: ${(cfg.allowedUsers?.length ?? 0) > 0 ? `${cfg.allowedUsers?.length} users` : 'everyone (users)'} / ${(cfg.allowedChats?.length ?? 0) > 0 ? `${cfg.allowedChats?.length} chats` : 'everyone (chats)'}`,\r\n `Notify: sessionEnd=${cfg.notifyOnSessionEnd ?? false}, longTool=${cfg.longToolThresholdMs ? `${cfg.longToolThresholdMs}ms` : 'off'}`,\r\n ];\r\n\r\n return { message: lines.join('\\n') };\r\n },\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// /telegram:send\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function tgSendCommand(\r\n bot: TelegramBot,\r\n defaultChatId: string | number | undefined,\r\n): SlashCommand {\r\n return {\r\n name: 'send',\r\n description: 'Send a message to a Telegram chat',\r\n help: `Usage: /telegram:send [chat_id] <message>\r\n\r\nSend a message to a Telegram chat.\r\n- First argument (optional): chat or user ID. Uses notifyChatId from config when omitted.\r\n- Everything else: the message text.\r\n\r\nExamples:\r\n /telegram:send 123456789 Build completed successfully ✓\r\n /telegram:send Deploy finished — check staging`,\r\n async run(args, _ctx) {\r\n if (!args.trim()) {\r\n return { message: 'Usage: /telegram:send [chat_id] <message>' };\r\n }\r\n\r\n let chatId: string | number;\r\n let text: string;\r\n\r\n // First token might be a numeric chat_id\r\n const parts = args.trim().split(/\\s+/);\r\n const maybeId = parts[0];\r\n if (/^\\d+$/.test(expectDefined(maybeId)) && parts.length > 1) {\r\n chatId = expectDefined(maybeId);\r\n text = parts.slice(1).join(' ');\r\n } else if (defaultChatId) {\r\n chatId = defaultChatId;\r\n text = args.trim();\r\n } else {\r\n return {\r\n message:\r\n 'No chat_id provided and no default notifyChatId configured.\\nUsage: /telegram:send <chat_id> <message>',\r\n };\r\n }\r\n\r\n try {\r\n const res = await bot.sendMessage(chatId, text);\r\n return {\r\n message: `✅ Message sent to ${chatId} (msg_id=${res.result?.message_id ?? '?'})`,\r\n };\r\n } catch (err) {\r\n return { message: `❌ Failed to send: ${(err as Error).message}` };\r\n }\r\n },\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// /telegram:chatid\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function tgChatIdCommand(defaultChatId?: string | number): SlashCommand {\r\n const chatIdStr = defaultChatId ? String(defaultChatId) : null;\r\n return {\r\n name: 'chatid',\r\n description: 'Show the configured default chat ID',\r\n help: `Usage: /telegram:chatid\r\n\r\nShows the current default notifyChatId used for notifications\r\nand the \\`telegram_send\\` tool when no chat_id is specified.`,\r\n async run(_args, _ctx) {\r\n if (chatIdStr) {\r\n return { message: `Configured notifyChatId: ${chatIdStr}` };\r\n }\r\n return { message: 'No notifyChatId configured. Set it in the plugin config or pass chat_id explicitly to telegram_send.' };\r\n },\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Register all\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function registerSlashCommands(\r\n api: PluginAPI,\r\n bot: TelegramBot,\r\n cfg: TelegramPluginConfig,\r\n): string[] {\r\n const cmds = [\r\n tgStatusCommand(bot, cfg),\r\n tgSendCommand(bot, cfg.notifyChatId),\r\n tgChatIdCommand(cfg.notifyChatId),\r\n ];\r\n for (const cmd of cmds) api.slashCommands.register(cmd);\r\n return cmds.map((c) => c.name);\r\n}\r\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 — summarize findings, report outcomes clearly, do not paste raw data.',\n usageHint: 'telegram_read(chat_id: \"123456789\", limit: 5, ack_last: 42) — 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 type { Tool } from '@wrongstack/core';\nimport type { Logger } from '@wrongstack/core';\nimport type { TelegramBot } from '../bot.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 /** Resolved at every execute() call so config changes take effect without restart. */\n getDefaultChatId(): string | number | undefined;\n maxMessageLength: number;\n log: Logger;\n}): Tool<TelegramSendInput> {\n return {\n name: 'telegram_send',\n description:\n 'Send a message to a Telegram chat. Write the message in natural prose — a human reads it. Summarize results, state what happened, and include only the key details. Never paste raw JSON, object dumps, or truncated tool output directly into the message field.',\n usageHint: 'telegram_send(chat_id: \"123456789\", message: \"Build completed — 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–4 lines for readability on mobile.',\n },\n },\n required: ['message'],\n },\n permission: 'confirm',\n mutating: true,\n timeoutMs: 15_000,\n async execute(input, _ctx, _opts) {\n const chatId = input.chat_id ?? opts.getDefaultChatId();\n if (!chatId) {\n throw new Error(\n 'No chat_id provided and no default notifyChatId configured. Set notifyChatId in plugin config or pass chat_id.',\n );\n }\n\n // Truncate message to fit Telegram's 4096 char limit\n const truncated = truncateForTelegram(input.message, opts.maxMessageLength);\n\n opts.log.info(`telegram_send → chat_id=${chatId} (${truncated.length} chars)`);\n\n const res = 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","import { expectDefined } from '@wrongstack/core';\nimport type { Config, Plugin } from '@wrongstack/core';\nimport { TelegramBot } from './bot.js';\nimport type { TelegramIncomingMessage } from './bot.js';\nimport { truncateForTelegram } from './bot.js';\nimport { PLUGIN_NAME, readTelegramConfig, telegramConfigSchema } from './config.js';\nimport { formatDelegateCompleted, formatSessionEnded, formatToolExecuted } from './format.js';\nimport type { SessionEndedLike, ToolExecutedLike } from './format.js';\nimport { PollLock, lockPathForToken } from './poll-lock.js';\nimport { registerSlashCommands } from './slash-commands/index.js';\nimport { makeTelegramReadTool } from './tools/telegram-read.js';\nimport { makeTelegramSendTool } from './tools/telegram-send.js';\n// ---------------------------------------------------------------------------\n// Teardown state\n// ---------------------------------------------------------------------------\n\n/** Mutable runtime config — updated via api.onConfigChange so changes take\n * effect without restarting the plugin. */\ninterface RuntimeConfig {\n notifyChatId: string | number | undefined;\n notifyOnSessionEnd: boolean;\n notifyOnDelegate: boolean;\n longToolThresholdMs: number;\n maxMessageLength: number;\n}\n\nlet teardownState: {\n offs: Array<() => void>;\n toolNames: string[];\n commandNames: string[];\n bot: TelegramBot;\n runtimeCfg: RuntimeConfig;\n} | null = null;\n\n/** Read the Telegram section from a full Config object. */\nfunction telegramFromConfig(cfg: Config): {\n notifyChatId: string | number | undefined;\n notifyOnSessionEnd: boolean;\n notifyOnDelegate: boolean;\n longToolThresholdMs: number;\n maxMessageLength: number;\n} {\n const ext = (cfg.extensions as Record<string, Record<string, unknown>> | undefined)?.[PLUGIN_NAME] ?? {};\n return {\n notifyChatId:\n ext.notifyChatId !== undefined ? String(ext.notifyChatId) : undefined,\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:\n 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 — 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 pollIntervalSec: 2,\n notifyOnSessionEnd: false,\n longToolThresholdMs: 30_000,\n maxMessageLength: 4000,\n },\n\n async setup(api) {\n const cfg = readTelegramConfig(api);\n const log = api.log;\n\n log.info('Starting Telegram plugin...');\n\n // ---- Mutable runtime config (updated via onConfigChange) ----\n const runtimeCfg: RuntimeConfig = {\n notifyChatId: cfg.notifyChatId,\n notifyOnSessionEnd: cfg.notifyOnSessionEnd ?? false,\n notifyOnDelegate: cfg.notifyOnDelegate ?? true,\n longToolThresholdMs: cfg.longToolThresholdMs ?? 30_000,\n maxMessageLength: cfg.maxMessageLength ?? 4000,\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 const bot = new TelegramBot({\n token: cfg.botToken,\n pollIntervalSec: cfg.pollIntervalSec ?? 2,\n allowedUsers: new Set((cfg.allowedUsers ?? []).map(String)),\n allowedChats: new Set((cfg.allowedChats ?? []).map(String)),\n bufferSize: 50,\n log,\n offsetStoragePath: cfg.offsetStoragePath,\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 // Log it for the user in the TUI\n const who = msg.userName ?? msg.userId ?? 'unknown';\n log.info(`📨 Telegram: ${who} (chat=${msg.chatId}): ${msg.text.slice(0, 200)}`);\n },\n });\n\n // ---- Register tools ----\n const sendTool = makeTelegramSendTool({\n bot,\n getDefaultChatId: () => runtimeCfg.notifyChatId,\n maxMessageLength: runtimeCfg.maxMessageLength,\n log,\n });\n const readTool = makeTelegramReadTool({ bot });\n api.tools.register(sendTool);\n api.tools.register(readTool);\n\n // ---- Event subscriptions ----\n const offs: Array<() => void> = [];\n\n // System prompt contributor — inject unread Telegram messages\n const unregisterPrompt = api.registerSystemPromptContributor(async () => {\n const msgs = bot.getMessages({ limit: 5 });\n if (msgs.length === 0) return [];\n\n const blocks: Array<{ type: 'text'; text: string }> = [\n {\n type: 'text',\n text: [\n '## Telegram Inbox',\n `You have ${bot.bufferCount} unread Telegram message(s).`,\n 'Read them with `telegram_read` and reply with `telegram_send`.',\n '',\n 'Recent messages:',\n ...msgs.map((m) => {\n const who = m.userName ?? `user_${m.userId ?? 'unknown'}`;\n const ts = new Date(m.timestamp).toLocaleTimeString();\n return `- [${ts}] **${who}** (chat=${m.chatId}): ${m.text.slice(0, 200)}`;\n }),\n '',\n ].join('\\n'),\n },\n ];\n return blocks;\n });\n offs.push(unregisterPrompt);\n\n // Register slash commands\n const commandNames = registerSlashCommands(api, bot, cfg);\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 offs.push(\n api.events.on('session.ended', (event) => {\n if (!runtimeCfg.notifyOnSessionEnd || !runtimeCfg.notifyChatId) return;\n const payload: SessionEndedLike = {\n id: 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 formatSessionEnded(payload),\n runtimeCfg.maxMessageLength,\n );\n void bot.sendMessage(expectDefined(runtimeCfg.notifyChatId), msg).catch((err) => {\n log.debug(`Failed to send session end notification: ${(err as Error).message}`);\n });\n }),\n );\n\n offs.push(\n api.events.on('tool.executed', (event) => {\n if (\n !runtimeCfg.notifyChatId ||\n runtimeCfg.longToolThresholdMs <= 0 ||\n event.durationMs < runtimeCfg.longToolThresholdMs\n ) return;\n const payload: ToolExecutedLike = {\n name: event.name,\n ok: event.ok,\n durationMs: event.durationMs,\n output: event.output,\n };\n const msg = truncateForTelegram(\n formatToolExecuted(payload),\n runtimeCfg.maxMessageLength,\n );\n void bot.sendMessage(expectDefined(runtimeCfg.notifyChatId), msg).catch((err) => {\n log.debug(`Failed to send tool notification: ${(err as Error).message}`);\n });\n }),\n );\n\n offs.push(\n api.events.on('delegate.completed', (event) => {\n if (!runtimeCfg.notifyOnDelegate || !runtimeCfg.notifyChatId) return;\n const msg = truncateForTelegram(\n formatDelegateCompleted(event),\n runtimeCfg.maxMessageLength,\n );\n void bot.sendMessage(expectDefined(runtimeCfg.notifyChatId), msg).catch((err) => {\n log.debug(`Failed to send delegate notification: ${(err as Error).message}`);\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 — no restart needed.\n const unlistenConfig = api.onConfigChange((next, _prev) => {\n const fresh = telegramFromConfig(next);\n runtimeCfg.notifyChatId = fresh.notifyChatId;\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 offs.push(unlistenConfig);\n\n // ---- Start polling ----\n bot.start();\n\n teardownState = {\n offs,\n toolNames: [sendTool.name, readTool.name],\n commandNames,\n bot,\n runtimeCfg,\n };\n\n log.info('Telegram plugin ready');\n },\n\n async teardown(api) {\n const state = teardownState;\n if (!state) return;\n teardownState = null;\n\n state.bot.stop();\n for (const off of state.offs) off();\n for (const name of state.toolNames) api.tools.unregister(name);\n for (const name of state.commandNames) {\n api.slashCommands.unregister(`${PLUGIN_NAME}:${name}`);\n }\n\n 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// Re-export the types consumers may want\nexport type { TelegramIncomingMessage } from './bot.js';\nexport type { TelegramPluginConfig } from './config.js';\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/bot.ts","../src/config.ts","../src/redact.ts","../src/format.ts","../src/poll-lock.ts","../src/slash-commands/index.ts","../src/tools/telegram-read.ts","../src/tools/telegram-send.ts","../src/tools/telegram-approve.ts","../src/index.ts"],"names":["readFileSync","writeFileSync","randomUUID","expectDefined"],"mappings":"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","file":"index.js","sourcesContent":["import type { Logger } from '@wrongstack/core';\nimport { sleep } from '@wrongstack/core/utils';\nimport type { PollLock } from './poll-lock.js';\n\n// ---------------------------------------------------------------------------\n// Redaction helpers\n// ---------------------------------------------------------------------------\n/** Redact the bot token from a URL for safe logging. */\nfunction redactToken(url: string, token: string): string {\n return url.replace(token, '[REDACTED]');\n}\n\n// ---------------------------------------------------------------------------\n// Telegram Bot API types (subset used by this plugin)\n// ---------------------------------------------------------------------------\n\ninterface TgUser {\n id: number;\n is_bot: boolean;\n first_name: string;\n username?: string | undefined;\n}\n\ninterface TgChat {\n id: number;\n type: 'private' | 'group' | 'supergroup' | 'channel';\n title?: string | undefined;\n username?: string | undefined;\n}\n\ninterface TgMessage {\n message_id: number;\n from?: TgUser | undefined;\n chat: TgChat;\n date: number;\n text?: string | undefined;\n}\n\ninterface TgUpdate {\n update_id: number;\n message?: TgMessage | undefined;\n edited_message?: TgMessage | undefined;\n /** Inline-keyboard press. `data` carries the action key (e.g. `approve:abc`). */\n callback_query?: TgCallbackQuery | undefined;\n}\n\ninterface TgCallbackQuery {\n id: string;\n from?: TgUser | undefined;\n /** Message the keyboard was attached to; preserved so we can edit/answer it. */\n message?: { message_id: number; chat: TgChat } | undefined;\n /** Action key chosen by the user (up to 64 bytes per Telegram docs). */\n data?: string | undefined;\n}\n\ninterface TgResponse<T> {\n ok: boolean;\n result?: T | undefined;\n description?: string | undefined;\n error_code?: number | undefined;\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\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 path to a file that stores the polling offset. When provided,\n * the offset is persisted on every successful poll and restored on startup,\n * preventing message replay after crashes or restarts.\n */\n offsetStoragePath?: string | 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 baseUrl: string;\n /** Base URL with token redacted, safe to use in log calls. */\n private readonly safeBaseUrl: string;\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 /** If set, the offset is persisted here after each successful poll. */\n private readonly offsetStoragePath?: string | 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 callback_query waiters keyed by the `data` string they\n // registered. Cleared on stop(). Used by `telegram_approve` to bridge a\n // synchronous-looking API onto the async callback event.\n private readonly callbackWaiters = new Map<\n string,\n { resolve: (value: { approved: boolean; fromUser: string }) => void; timer: ReturnType<typeof setTimeout> }\n >();\n\n constructor(opts: TelegramBotOptions) {\n this.baseUrl = `https://api.telegram.org/bot${opts.token}`;\n this.safeBaseUrl = redactToken(this.baseUrl, 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.offsetStoragePath = opts.offsetStoragePath;\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.offsetStoragePath) {\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 [key, waiter] of this.callbackWaiters) {\n clearTimeout(waiter.timer);\n waiter.resolve({ approved: false, fromUser: 'shutdown' });\n this.callbackWaiters.delete(key);\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 — 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 — taking over polling.');\n } else {\n this.log.info(`Telegram bot polling started (${this.safeBaseUrl})`);\n }\n this.schedulePoll();\n }\n\n /** The lock was stolen while we held it — 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('Telegram: poll lock lost to another instance — pausing polling and standing by.');\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 — incoming messages the agent can read\n // ------------------------------------------------------------------\n\n /** Return buffered messages, newest first. Optionally filter by chat. */\n getMessages(opts?: { chatId?: string | number | undefined; limit?: number | undefined }): 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 — send a message\n // ------------------------------------------------------------------\n\n async sendMessage(chatId: string | number, text: string): Promise<TgResponse<TgMessage>> {\n const url = `${this.baseUrl}/sendMessage`;\n const body = JSON.stringify({\n chat_id: String(chatId),\n text,\n disable_web_page_preview: true,\n });\n\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 res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n signal: AbortSignal.timeout(10_000),\n });\n const data = (await res.json()) as TgResponse<TgMessage>;\n if (!data.ok) {\n throw new Error(`Telegram API error ${data.error_code}: ${data.description}`);\n }\n return data;\n } catch (err) {\n lastErr = err;\n if (attempt < 3) {\n this.log.debug(`Telegram sendMessage attempt ${attempt} failed, retrying in 1s...`);\n await sleep(1000);\n }\n }\n }\n throw lastErr;\n }\n\n // ------------------------------------------------------------------\n // Outgoing — 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 — callers\n * pass already-encoded `callback_data` strings (≤ 64 bytes each).\n */\n async sendMessageWithKeyboard(\n chatId: string | number,\n text: string,\n buttons: Array<{ text: string; callback_data: string }>,\n ): Promise<TgResponse<TgMessage>> {\n const url = `${this.baseUrl}/sendMessage`;\n const body = JSON.stringify({\n chat_id: String(chatId),\n text,\n disable_web_page_preview: true,\n reply_markup: {\n inline_keyboard: [buttons.map((b) => ({ text: b.text, callback_data: b.callback_data }))],\n },\n });\n\n let lastErr: unknown;\n for (let attempt = 1; attempt <= 3; attempt++) {\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n signal: AbortSignal.timeout(10_000),\n });\n const data = (await res.json()) as TgResponse<TgMessage>;\n if (!data.ok) {\n throw new Error(`Telegram API error ${data.error_code}: ${data.description}`);\n }\n return data;\n } catch (err) {\n lastErr = err;\n if (attempt < 3) await sleep(1000);\n }\n }\n throw lastErr;\n }\n\n // ------------------------------------------------------------------\n // Health\n // ------------------------------------------------------------------\n\n async health(): Promise<{ ok: boolean; username?: string | undefined; error?: string | undefined }> {\n // Use a dedicated AbortController per call so stop() can cancel an\n // in-flight health check, AND so the 5 s timeout timer can be cleared\n // immediately on completion (a leaked AbortSignal.timeout timer\n // would keep the test worker alive for up to 5 s after each call).\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), 5000);\n try {\n const url = `${this.baseUrl}/getMe`;\n const res = await fetch(url, { signal: ctrl.signal });\n const data = (await res.json()) as TgResponse<TgUser>;\n if (!data.ok || !data.result) {\n return { ok: false, error: data.description ?? 'Unknown error' };\n }\n return { ok: true, username: data.result.username };\n } catch (err) {\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 — 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 url = `${this.baseUrl}/getUpdates?offset=${this.offset}&timeout=10`;\n const res = await fetch(url, { signal: this.controller.signal });\n const data = (await res.json()) as TgResponse<TgUpdate[]>;\n\n if (!data.ok) {\n if (data.error_code === 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) — 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) — backing off to 60s polls until it stops.',\n );\n }\n }\n this.log.debug(`Telegram getUpdates failed: ${data.description}`);\n return;\n }\n this.conflictStreak = 0;\n\n const updates = data.result ?? [];\n for (const upd of updates) {\n this.offset = upd.update_id + 1;\n\n // Inline-keyboard press — must be answered within 10 s or the\n // Telegram client shows a \"loading\" spinner forever.\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 const msg = { ...raw, text: raw.text };\n this.processMessage(msg);\n }\n\n // Persist offset after each successful poll to prevent message replay\n // after crashes or restarts.\n if (this.offsetStoragePath && this.offset > 0) {\n void this.saveOffset();\n }\n } catch (err) {\n if ((err as Error).name === 'AbortError') return;\n this.log.debug(`Telegram poll error: ${(err as Error).message}`);\n }\n }\n\n private processMessage(msg: TgMessage & { text: string }): void {\n const chatId = String(msg.chat.id);\n const userId = msg.from ? String(msg.from.id) : undefined;\n\n // Allowlist checks\n if (this.allowedUsers.size > 0 && userId && !this.allowedUsers.has(userId)) {\n this.log.debug(`Ignoring message from user ${userId} (not in allowedUsers)`);\n void this.sendMessage(chatId, '⛔ You are not authorized to interact with this bot.');\n return;\n }\n if (this.allowedChats.size > 0 && !this.allowedChats.has(chatId)) {\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 * Handle an inbound `callback_query` update: route it to a registered\n * waiter (if any), and acknowledge it via `answerCallbackQuery` so the\n * client stops spinning. Telegram requires the answer within 10 s.\n */\n private async dispatchCallback(cq: TgCallbackQuery): Promise<void> {\n const key = cq.data ?? '';\n const waiter = key !== '' ? this.callbackWaiters.get(key) : undefined;\n const approved = key.endsWith(':yes');\n const fromUser = cq.from?.username ?? cq.from?.first_name ?? 'unknown';\n\n try {\n await fetch(`${this.baseUrl}/answerCallbackQuery`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n callback_query_id: cq.id,\n text: approved ? 'Approved ✓' : 'Denied ✗',\n show_alert: false,\n }),\n signal: AbortSignal.timeout(5_000),\n });\n } catch (err) {\n this.log.debug(`answerCallbackQuery failed: ${(err as Error).message}`);\n }\n\n if (waiter) {\n clearTimeout(waiter.timer);\n this.callbackWaiters.delete(key);\n waiter.resolve({ approved, fromUser });\n } else {\n this.log.debug(`Unmatched callback_query data=\"${key}\" (no pending waiter)`);\n }\n }\n\n /**\n * Register a waiter for a callback_query whose `data` field equals `key`.\n * Resolves with `{ approved, fromUser }` when a matching press arrives, or\n * with `{ approved: false, fromUser: 'timeout' }` after `timeoutMs`.\n *\n * Callers are responsible for not registering the same key twice — a\n * second `awaitCallback` for an in-flight key is undefined.\n */\n awaitCallback(\n key: string,\n timeoutMs: number,\n ): Promise<{ approved: boolean; fromUser: string }> {\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n if (this.callbackWaiters.delete(key)) {\n resolve({ approved: false, fromUser: 'timeout' });\n }\n }, timeoutMs);\n // The timer is intentionally NOT unref'd: a pending callback waiter\n // represents an outstanding user-facing request that must resolve\n // (or time out) before the process can safely exit. unref'ing it\n // would let the event loop drain prematurely during long waits.\n this.callbackWaiters.set(key, { resolve, timer });\n });\n }\n\n private async loadOffset(): Promise<void> {\n if (!this.offsetStoragePath) return;\n try {\n const { readFileSync } = await import('node:fs');\n const raw = readFileSync(this.offsetStoragePath, 'utf8').trim();\n const n = Number.parseInt(raw, 10);\n if (Number.isFinite(n) && n >= 0) {\n this.offset = n;\n this.log.debug(`Telegram polling offset restored: ${this.offset}`);\n }\n } catch {\n // File doesn't exist yet — start from 0, which is correct.\n }\n }\n\n private async saveOffset(): Promise<void> {\n if (!this.offsetStoragePath) return;\n try {\n const { writeFileSync } = await import('node:fs');\n // Write atomically so a crash mid-write can't leave a corrupt file.\n writeFileSync(this.offsetStoragePath, String(this.offset), 'utf8');\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 \"…\" to signal intentional truncation.\n */\nexport function truncateForTelegram(text: string, maxLen = 4000): string {\n if (text.length <= maxLen) return text;\n\n // Reserve room for truncation suffix\n const cutoff = maxLen - 30;\n if (cutoff <= 0) return `${text.slice(0, maxLen - 1)}…`;\n\n const searchEnd = Math.min(text.length, maxLen);\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…`;\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…`;\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)}…`;\n }\n\n // 4. Word boundary (space)\n const spaceIdx = text.lastIndexOf(' ', searchEnd);\n if (spaceIdx > cutoff) {\n return `${text.slice(0, spaceIdx)} …`;\n }\n\n // 5. Hard cut\n return `${text.slice(0, maxLen - 20)}…[+${text.length - maxLen + 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\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n}\n","import type { PluginAPI } from '@wrongstack/core';\r\n\r\nexport const PLUGIN_NAME = 'telegram';\r\n\r\nexport interface TelegramPluginConfig {\r\n /** Telegram Bot API token (from @BotFather). */\r\n botToken: string;\r\n /**\r\n * Default chat ID for outgoing notifications.\r\n * The agent's `telegram_send` tool can override per-call.\r\n */\r\n notifyChatId?: string | number | undefined;\r\n /**\r\n * List of user/chat IDs allowed to interact with the bot.\r\n * Empty = allow all. Recommended to set in production.\r\n */\r\n allowedUsers?: Array<string | number> | undefined;\r\n /**\r\n * List of group/chat IDs the bot is allowed to read from.\r\n * Empty = allow all. Narrow this to prevent noise.\r\n */\r\n allowedChats?: Array<string | number> | undefined;\r\n /** Polling interval in seconds (default: 2). */\r\n pollIntervalSec?: number | undefined;\r\n /** Notify on Telegram when a session ends. */\r\n notifyOnSessionEnd?: boolean | undefined;\r\n /** Notify when a tool runs longer than this threshold (ms). Set 0 to disable. */\r\n longToolThresholdMs?: number | undefined;\r\n /** Notify (humanized) when a `delegate` subagent finishes. Default: true. */\r\n notifyOnDelegate?: boolean | undefined;\r\n /** Maximum message length for Telegram (Telegram caps at 4096). */\r\n maxMessageLength?: number | undefined;\r\n /**\r\n * Path to a file that stores the Telegram polling offset. When set,\r\n * the offset is persisted on every successful poll and restored on startup,\r\n * preventing message replay after crashes or restarts.\r\n * The directory must already exist and be writable.\r\n */\r\n offsetStoragePath?: string | undefined;\r\n /**\r\n * Elect a single poller per bot token across wstack instances (default:\r\n * true). Telegram allows one `getUpdates` consumer per token; without this,\r\n * two instances sharing a token fight and get HTTP 409 on every poll.\r\n * Extra instances stand by and take over when the active poller stops.\r\n * Set false only if this is guaranteed to be the sole consumer.\r\n */\r\n singleInstanceLock?: boolean | undefined;\r\n}\r\n\r\nexport const DEFAULT_CONFIG: Required<Omit<TelegramPluginConfig, 'botToken' | 'notifyChatId' | 'offsetStoragePath'>> = {\r\n allowedUsers: [],\r\n allowedChats: [],\r\n pollIntervalSec: 2,\r\n notifyOnSessionEnd: false,\r\n longToolThresholdMs: 30_000,\r\n notifyOnDelegate: true,\r\n maxMessageLength: 4000,\r\n singleInstanceLock: true,\r\n};\r\n\r\nexport const telegramConfigSchema = {\r\n type: 'object',\r\n properties: {\r\n botToken: { type: 'string', description: 'Telegram Bot API token from @BotFather' },\r\n notifyChatId: {\r\n oneOf: [{ type: 'string' }, { type: 'integer' }],\r\n description: 'Default chat ID for outgoing notifications',\r\n },\r\n allowedUsers: {\r\n type: 'array',\r\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\r\n description: 'User IDs allowed to interact with the bot',\r\n },\r\n allowedChats: {\r\n type: 'array',\r\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\r\n description: 'Chat IDs the bot is allowed to read from',\r\n },\r\n pollIntervalSec: {\r\n type: 'integer',\r\n minimum: 1,\r\n maximum: 60,\r\n description: 'Polling interval in seconds',\r\n },\r\n notifyOnSessionEnd: { type: 'boolean' },\r\n longToolThresholdMs: { type: 'integer', minimum: 0 },\r\n notifyOnDelegate: { type: 'boolean' },\r\n maxMessageLength: { type: 'integer', minimum: 100, maximum: 4096 },\r\n singleInstanceLock: {\r\n type: 'boolean',\r\n description: 'Elect a single getUpdates poller per bot token across wstack instances (default true)',\r\n },\r\n },\r\n required: ['botToken'],\r\n};\r\n\r\nexport function readTelegramConfig(\r\n api: Pick<PluginAPI, 'config'>,\r\n): Required<Omit<TelegramPluginConfig, 'notifyChatId' | 'offsetStoragePath'>> &\r\n Pick<TelegramPluginConfig, 'notifyChatId' | 'offsetStoragePath'> {\r\n const config = api.config as never as Record<string, unknown>;\r\n const extensions = config.extensions as Record<string, unknown> | undefined;\r\n const pluginEntries = config.plugins;\r\n const legacyPlugins = pluginEntries as Record<string, unknown> | undefined;\r\n const legacyOpts =\r\n legacyPlugins && !Array.isArray(legacyPlugins) ? legacyPlugins[PLUGIN_NAME] : undefined;\r\n const entryOpts = pluginOptionsFromEntries(pluginEntries);\r\n const opts = {\r\n ...((legacyOpts ?? entryOpts) as TelegramPluginConfig),\r\n ...((extensions?.[PLUGIN_NAME] ?? {}) as TelegramPluginConfig),\r\n };\r\n return {\r\n ...DEFAULT_CONFIG,\r\n ...opts,\r\n };\r\n}\r\n\r\nfunction pluginOptionsFromEntries(entries: unknown): TelegramPluginConfig | undefined {\r\n if (!Array.isArray(entries)) return undefined;\r\n const found = entries.find(\r\n (entry) =>\r\n typeof entry === 'object' &&\r\n entry !== null &&\r\n 'name' in entry &&\r\n ((entry as { name?: unknown | undefined }).name === '@wrongstack/telegram' ||\r\n (entry as { name?: unknown | undefined }).name === PLUGIN_NAME),\r\n ) as { name?: unknown | undefined; options?: unknown | undefined } | undefined;\r\n return found?.options && typeof found.options === 'object'\r\n ? (found.options as TelegramPluginConfig)\r\n : undefined;\r\n}\r\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 — tool output printed by a long bash\n// run is forwarded verbatim to a phone notification — 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 — 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, …\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 — 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 — `[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) — 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 — status markers only, no decoration.\n// - Run output through `redactSecrets` before formatting — 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 — 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… +${lines.length - 3} more lines`;\n if (preview.length > 300) preview = `${preview.slice(0, 297)}…`;\n return preview;\n}\n\n// ---------------------------------------------------------------------------\n// Event → message formatters\n// ---------------------------------------------------------------------------\n\n/**\n * Render a finished delegation as a readable Telegram message.\n *\n * Example:\n * ✅ Delegate → bug-hunter · success\n * Found 3 null-deref risks in auth.ts and patched the worst one…\n * ⏱ 3m · 4 iter · 37 tools · 💲0.0820\n */\nexport function formatDelegateCompleted(e: DelegateCompletedLike): string {\n const icon = e.ok ? '✅' : '❌';\n const status = e.status ?? (e.ok ? 'success' : 'failed');\n const task = e.task.length > 160 ? `${e.task.slice(0, 159)}…` : 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` — a\n // delegate summary can contain raw tool output that itself leaks tokens.\n const rawBody = e.summary?.trim() || `(no summary) — ${task}`;\n const body = redactSecrets(rawBody);\n\n const stats = [\n `⏱ ${fmtDuration(e.durationMs)}`,\n `${e.iterations} iter`,\n `${e.toolCalls} tools`,\n ];\n if (typeof e.costUsd === 'number' && e.costUsd > 0) {\n stats.push(`💲${e.costUsd.toFixed(4)}`);\n }\n\n return [`${icon} Delegate → ${e.target} · ${status}`, body, stats.join(' · ')].join('\\n');\n}\n\n/**\n * Render a long-running tool execution notification.\n *\n * Example:\n * ✅ bash completed in 45.2s\n * pnpm test — 12 suites, 47 tests passed\n * …\n */\nexport function formatToolExecuted(e: ToolExecutedLike): string {\n const icon = e.ok ? '✅' : '❌';\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 * 🏁 Session sess_abcd ended\n * ⬇ 8,234 in · ⬆ 3,456 out · 11,690 total\n * Cache: 1,200 read · 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 `🏁 Session ${id} ended`,\n `⬇ ${fmtTokens(e.inputTokens)} in · ⬆ ${fmtTokens(e.outputTokens)} out · ${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(`📦 ${parts.join(' · ')}`);\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 — `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 — 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 — 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 { expectDefined } from '@wrongstack/core';\nimport type { PluginAPI, SlashCommand } from '@wrongstack/core';\r\nimport type { TelegramBot } from '../bot.js';\r\nimport type { TelegramPluginConfig } from '../config.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// /telegram:status\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function tgStatusCommand(bot: TelegramBot, cfg: TelegramPluginConfig): SlashCommand {\r\n return {\r\n name: 'status',\r\n aliases: ['tgstat', 'tgs'],\r\n description: 'Show Telegram bot connection status and config',\r\n help: `Usage: /telegram:status\r\n\r\nShows whether the bot is connected, its username, polling interval,\r\nallowlist status, and notification settings.`,\r\n async run(_args, _ctx) {\r\n const health = await bot.health();\r\n const lines = [\r\n '═══ Telegram Plugin Status ═══',\r\n '',\r\n `Bot: ${health.ok ? `✅ @${health.username ?? 'connected'}` : `❌ ${health.error ?? 'offline'}`}`,\r\n `Running: ${bot.running ? 'yes' : 'no'}`,\r\n `Started: ${bot.startedAt ? new Date(bot.startedAt).toLocaleTimeString() : 'N/A'}`,\r\n `Poll: every ${cfg.pollIntervalSec ?? 2}s`,\r\n `Allowed: ${(cfg.allowedUsers?.length ?? 0) > 0 ? `${cfg.allowedUsers?.length} users` : 'everyone (users)'} / ${(cfg.allowedChats?.length ?? 0) > 0 ? `${cfg.allowedChats?.length} chats` : 'everyone (chats)'}`,\r\n `Notify: sessionEnd=${cfg.notifyOnSessionEnd ?? false}, longTool=${cfg.longToolThresholdMs ? `${cfg.longToolThresholdMs}ms` : 'off'}`,\r\n ];\r\n\r\n return { message: lines.join('\\n') };\r\n },\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// /telegram:send\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function tgSendCommand(\r\n bot: TelegramBot,\r\n defaultChatId: string | number | undefined,\r\n): SlashCommand {\r\n return {\r\n name: 'send',\r\n description: 'Send a message to a Telegram chat',\r\n help: `Usage: /telegram:send [chat_id] <message>\r\n\r\nSend a message to a Telegram chat.\r\n- First argument (optional): chat or user ID. Uses notifyChatId from config when omitted.\r\n- Everything else: the message text.\r\n\r\nExamples:\r\n /telegram:send 123456789 Build completed successfully ✓\r\n /telegram:send Deploy finished — check staging`,\r\n async run(args, _ctx) {\r\n if (!args.trim()) {\r\n return { message: 'Usage: /telegram:send [chat_id] <message>' };\r\n }\r\n\r\n let chatId: string | number;\r\n let text: string;\r\n\r\n // First token might be a numeric chat_id\r\n const parts = args.trim().split(/\\s+/);\r\n const maybeId = parts[0];\r\n if (/^\\d+$/.test(expectDefined(maybeId)) && parts.length > 1) {\r\n chatId = expectDefined(maybeId);\r\n text = parts.slice(1).join(' ');\r\n } else if (defaultChatId) {\r\n chatId = defaultChatId;\r\n text = args.trim();\r\n } else {\r\n return {\r\n message:\r\n 'No chat_id provided and no default notifyChatId configured.\\nUsage: /telegram:send <chat_id> <message>',\r\n };\r\n }\r\n\r\n try {\r\n const res = await bot.sendMessage(chatId, text);\r\n return {\r\n message: `✅ Message sent to ${chatId} (msg_id=${res.result?.message_id ?? '?'})`,\r\n };\r\n } catch (err) {\r\n return { message: `❌ Failed to send: ${(err as Error).message}` };\r\n }\r\n },\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// /telegram:chatid\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function tgChatIdCommand(defaultChatId?: string | number): SlashCommand {\r\n const chatIdStr = defaultChatId ? String(defaultChatId) : null;\r\n return {\r\n name: 'chatid',\r\n description: 'Show the configured default chat ID',\r\n help: `Usage: /telegram:chatid\r\n\r\nShows the current default notifyChatId used for notifications\r\nand the \\`telegram_send\\` tool when no chat_id is specified.`,\r\n async run(_args, _ctx) {\r\n if (chatIdStr) {\r\n return { message: `Configured notifyChatId: ${chatIdStr}` };\r\n }\r\n return { message: 'No notifyChatId configured. Set it in the plugin config or pass chat_id explicitly to telegram_send.' };\r\n },\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Register all\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function registerSlashCommands(\r\n api: PluginAPI,\r\n bot: TelegramBot,\r\n cfg: TelegramPluginConfig,\r\n): string[] {\r\n const cmds = [\r\n tgStatusCommand(bot, cfg),\r\n tgSendCommand(bot, cfg.notifyChatId),\r\n tgChatIdCommand(cfg.notifyChatId),\r\n ];\r\n for (const cmd of cmds) api.slashCommands.register(cmd);\r\n return cmds.map((c) => c.name);\r\n}\r\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 — summarize findings, report outcomes clearly, do not paste raw data.',\n usageHint: 'telegram_read(chat_id: \"123456789\", limit: 5, ack_last: 42) — 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 type { Tool } from '@wrongstack/core';\nimport type { Logger } from '@wrongstack/core';\nimport type { TelegramBot } from '../bot.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 /** Resolved at every execute() call so config changes take effect without restart. */\n getDefaultChatId(): string | number | undefined;\n maxMessageLength: number;\n log: Logger;\n}): Tool<TelegramSendInput> {\n return {\n name: 'telegram_send',\n description:\n 'Send a message to a Telegram chat. Write the message in natural prose — a human reads it. Summarize results, state what happened, and include only the key details. Never paste raw JSON, object dumps, or truncated tool output directly into the message field.',\n usageHint: 'telegram_send(chat_id: \"123456789\", message: \"Build completed — 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–4 lines for readability on mobile.',\n },\n },\n required: ['message'],\n },\n permission: 'confirm',\n mutating: true,\n timeoutMs: 15_000,\n async execute(input, _ctx, _opts) {\n const chatId = input.chat_id ?? opts.getDefaultChatId();\n if (!chatId) {\n throw new Error(\n 'No chat_id provided and no default notifyChatId configured. Set notifyChatId in plugin config or pass chat_id.',\n );\n }\n\n // Truncate message to fit Telegram's 4096 char limit\n const truncated = truncateForTelegram(input.message, opts.maxMessageLength);\n\n opts.log.info(`telegram_send → chat_id=${chatId} (${truncated.length} chars)`);\n\n const res = 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","import { randomUUID } from 'node:crypto';\nimport type { Tool } from '@wrongstack/core';\nimport type { Logger } from '@wrongstack/core';\nimport type { TelegramBot } from '../bot.js';\nimport { truncateForTelegram } from '../bot.js';\n\ninterface TelegramApproveInput {\n /** Short label for what's being approved (≤ 60 chars). Shown as the prompt heading. */\n prompt: string;\n /** Optional details (≤ 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 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 — for that, see the future B4 work.\n *\n * Permission: `auto` (NOT `confirm`). This is intentional — 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 getDefaultChatId(): string | number | undefined;\n maxMessageLength: number;\n log: Logger;\n}): Tool<TelegramApproveInput, TelegramApproveOutput> {\n return {\n name: 'telegram_approve',\n description:\n 'Post a yes/no approval prompt to a Telegram chat with inline keyboard buttons, and wait for the user to tap one. Returns { approved, from } where approved=false on timeout or explicit deny. Use this when you need explicit human confirmation before proceeding (destructive ops, irreversible deploys, ambiguous choices).',\n usageHint: '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: '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: false,\n timeoutMs: 610_000,\n async execute(input, _ctx, _toolOpts) {\n const chatId = input.chat_id ?? opts.getDefaultChatId();\n if (!chatId) {\n throw new Error(\n 'No chat_id provided and no default notifyChatId configured. Set notifyChatId in plugin config or pass chat_id.',\n );\n }\n const timeoutMs = Math.min(Math.max(input.timeout_ms ?? 60_000, 1000), 600_000);\n\n // Stable key so the callback update can be matched back to this call.\n const token = randomUUID().slice(0, 16);\n const yesKey = `approve:${token}:yes`;\n const noKey = `approve:${token}:no`;\n\n const heading = `⚠️ ${input.prompt}`;\n const detailsLine = input.details ? `\\n\\n${truncateForTelegram(input.details, 800)}` : '';\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 → chat_id=${chatId} prompt=\"${input.prompt.slice(0, 80)}\" token=${token}`);\n\n let promptMessageId: number | undefined;\n try {\n const sent = await opts.bot.sendMessageWithKeyboard(chatId, text, [\n { text: '✅ Approve', callback_data: yesKey },\n { text: '❌ Deny', callback_data: noKey },\n ]);\n promptMessageId = sent.result?.message_id;\n } catch (err) {\n opts.log.debug(`telegram_approve send failed: ${(err as Error).message}`);\n // Fall through — race the callback. If the message never lands the\n // race still resolves at timeout.\n }\n\n // Race the two buttons. First one wins; the other is ignored when\n // it arrives because the waiter is already deleted.\n const result = await Promise.race([\n opts.bot.awaitCallback(yesKey, timeoutMs),\n opts.bot.awaitCallback(noKey, timeoutMs),\n ]);\n\n return {\n approved: result.approved,\n from: result.fromUser,\n prompt_message_id: promptMessageId,\n };\n },\n };\n}","import { expectDefined } from '@wrongstack/core';\nimport type { Config, Plugin } from '@wrongstack/core';\nimport { TelegramBot } from './bot.js';\nimport type { TelegramIncomingMessage } from './bot.js';\nimport { truncateForTelegram } from './bot.js';\nimport { PLUGIN_NAME, readTelegramConfig, telegramConfigSchema } from './config.js';\nimport { formatDelegateCompleted, formatSessionEnded, formatToolExecuted } from './format.js';\nimport type { SessionEndedLike, ToolExecutedLike } from './format.js';\nimport { PollLock, lockPathForToken } from './poll-lock.js';\nimport { registerSlashCommands } from './slash-commands/index.js';\nimport { makeTelegramReadTool } from './tools/telegram-read.js';\nimport { makeTelegramSendTool } from './tools/telegram-send.js';\nimport { makeTelegramApproveTool } from './tools/telegram-approve.js';\n// ---------------------------------------------------------------------------\n// Teardown state\n// ---------------------------------------------------------------------------\n\n/** Mutable runtime config — updated via api.onConfigChange so changes take\n * effect without restarting the plugin. */\ninterface RuntimeConfig {\n notifyChatId: string | number | undefined;\n notifyOnSessionEnd: boolean;\n notifyOnDelegate: boolean;\n longToolThresholdMs: number;\n maxMessageLength: number;\n}\n\nlet teardownState: {\n offs: Array<() => void>;\n toolNames: string[];\n commandNames: string[];\n bot: TelegramBot;\n runtimeCfg: RuntimeConfig;\n} | null = null;\n\n/** Read the Telegram section from a full Config object. */\nfunction telegramFromConfig(cfg: Config): {\n notifyChatId: string | number | undefined;\n notifyOnSessionEnd: boolean;\n notifyOnDelegate: boolean;\n longToolThresholdMs: number;\n maxMessageLength: number;\n} {\n const ext = (cfg.extensions as Record<string, Record<string, unknown>> | undefined)?.[PLUGIN_NAME] ?? {};\n return {\n notifyChatId:\n ext.notifyChatId !== undefined ? String(ext.notifyChatId) : undefined,\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:\n 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 — 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 pollIntervalSec: 2,\n notifyOnSessionEnd: false,\n longToolThresholdMs: 30_000,\n maxMessageLength: 4000,\n },\n\n async setup(api) {\n const cfg = readTelegramConfig(api);\n const log = api.log;\n\n log.info('Starting Telegram plugin...');\n\n // ---- Mutable runtime config (updated via onConfigChange) ----\n const runtimeCfg: RuntimeConfig = {\n notifyChatId: cfg.notifyChatId,\n notifyOnSessionEnd: cfg.notifyOnSessionEnd ?? false,\n notifyOnDelegate: cfg.notifyOnDelegate ?? true,\n longToolThresholdMs: cfg.longToolThresholdMs ?? 30_000,\n maxMessageLength: cfg.maxMessageLength ?? 4000,\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 const bot = new TelegramBot({\n token: cfg.botToken,\n pollIntervalSec: cfg.pollIntervalSec ?? 2,\n allowedUsers: new Set((cfg.allowedUsers ?? []).map(String)),\n allowedChats: new Set((cfg.allowedChats ?? []).map(String)),\n bufferSize: 50,\n log,\n offsetStoragePath: cfg.offsetStoragePath,\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 // Log it for the user in the TUI\n const who = msg.userName ?? msg.userId ?? 'unknown';\n log.info(`📨 Telegram: ${who} (chat=${msg.chatId}): ${msg.text.slice(0, 200)}`);\n },\n });\n\n // ---- Register tools ----\n const sendTool = makeTelegramSendTool({\n bot,\n getDefaultChatId: () => runtimeCfg.notifyChatId,\n maxMessageLength: runtimeCfg.maxMessageLength,\n log,\n });\n const readTool = makeTelegramReadTool({ bot });\n const approveTool = makeTelegramApproveTool({\n bot,\n getDefaultChatId: () => runtimeCfg.notifyChatId,\n maxMessageLength: runtimeCfg.maxMessageLength,\n log,\n });\n api.tools.register(sendTool);\n api.tools.register(readTool);\n api.tools.register(approveTool);\n\n // ---- Event subscriptions ----\n const offs: Array<() => void> = [];\n\n // System prompt contributor — inject unread Telegram messages\n const unregisterPrompt = api.registerSystemPromptContributor(async () => {\n const msgs = bot.getMessages({ limit: 5 });\n if (msgs.length === 0) return [];\n\n const blocks: Array<{ type: 'text'; text: string }> = [\n {\n type: 'text',\n text: [\n '## Telegram Inbox',\n `You have ${bot.bufferCount} unread Telegram message(s).`,\n 'Read them with `telegram_read` and reply with `telegram_send`.',\n '',\n 'Recent messages:',\n ...msgs.map((m) => {\n const who = m.userName ?? `user_${m.userId ?? 'unknown'}`;\n const ts = new Date(m.timestamp).toLocaleTimeString();\n return `- [${ts}] **${who}** (chat=${m.chatId}): ${m.text.slice(0, 200)}`;\n }),\n '',\n ].join('\\n'),\n },\n ];\n return blocks;\n });\n offs.push(unregisterPrompt);\n\n // Register slash commands\n const commandNames = registerSlashCommands(api, bot, cfg);\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 offs.push(\n api.events.on('session.ended', (event) => {\n if (!runtimeCfg.notifyOnSessionEnd || !runtimeCfg.notifyChatId) return;\n const payload: SessionEndedLike = {\n id: 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 formatSessionEnded(payload),\n runtimeCfg.maxMessageLength,\n );\n void bot.sendMessage(expectDefined(runtimeCfg.notifyChatId), msg).catch((err) => {\n log.debug(`Failed to send session end notification: ${(err as Error).message}`);\n });\n }),\n );\n\n offs.push(\n api.events.on('tool.executed', (event) => {\n if (\n !runtimeCfg.notifyChatId ||\n runtimeCfg.longToolThresholdMs <= 0 ||\n event.durationMs < runtimeCfg.longToolThresholdMs\n ) return;\n const payload: ToolExecutedLike = {\n name: event.name,\n ok: event.ok,\n durationMs: event.durationMs,\n output: event.output,\n };\n const msg = truncateForTelegram(\n formatToolExecuted(payload),\n runtimeCfg.maxMessageLength,\n );\n void bot.sendMessage(expectDefined(runtimeCfg.notifyChatId), msg).catch((err) => {\n log.debug(`Failed to send tool notification: ${(err as Error).message}`);\n });\n }),\n );\n\n offs.push(\n api.events.on('delegate.completed', (event) => {\n if (!runtimeCfg.notifyOnDelegate || !runtimeCfg.notifyChatId) return;\n const msg = truncateForTelegram(\n formatDelegateCompleted(event),\n runtimeCfg.maxMessageLength,\n );\n void bot.sendMessage(expectDefined(runtimeCfg.notifyChatId), msg).catch((err) => {\n log.debug(`Failed to send delegate notification: ${(err as Error).message}`);\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 — no restart needed.\n const unlistenConfig = api.onConfigChange((next, _prev) => {\n const fresh = telegramFromConfig(next);\n runtimeCfg.notifyChatId = fresh.notifyChatId;\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 offs.push(unlistenConfig);\n\n // ---- Startup self-test (C7) ----\n // Verify the bot token is live before we start polling. A 401/403 here\n // means a wrong/rotated/revoked token — fail loud so the user sees the\n // misconfiguration immediately, rather than silently spinning and\n // logging HTTP 401 on every poll cycle.\n const probe = await bot.health();\n if (!probe.ok) {\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 // ---- Start polling ----\n bot.start();\n\n teardownState = {\n offs,\n toolNames: [sendTool.name, readTool.name, approveTool.name],\n commandNames,\n bot,\n runtimeCfg,\n };\n\n log.info('Telegram plugin ready');\n },\n\n async teardown(api) {\n const state = teardownState;\n if (!state) return;\n teardownState = null;\n\n state.bot.stop();\n for (const off of state.offs) off();\n for (const name of state.toolNames) api.tools.unregister(name);\n for (const name of state.commandNames) {\n api.slashCommands.unregister(`${PLUGIN_NAME}:${name}`);\n }\n\n 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// Re-export the types consumers may want\nexport type { TelegramIncomingMessage } from './bot.js';\nexport type { TelegramPluginConfig } from './config.js';\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/telegram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.275.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "WrongStack plugin — Telegram bridge: send messages, receive prompts, get notified.",
|
|
6
6
|
"repository": {
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"dist"
|
|
26
26
|
],
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@wrongstack/core": "0.
|
|
28
|
+
"@wrongstack/core": "0.275.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^25.9.3",
|
|
32
32
|
"tsup": "^8.5.1",
|
|
33
33
|
"typescript": "^6.0.3",
|
|
34
|
-
"@wrongstack/core": "0.
|
|
34
|
+
"@wrongstack/core": "0.275.0"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|