@agentprojectcontext/apx 1.42.0 → 1.42.2

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.
@@ -27,33 +27,16 @@
27
27
  // "poll_interval_ms": 1500
28
28
  // }
29
29
 
30
- import fs from "node:fs";
31
- import path from "node:path";
32
- import { TELEGRAM_STATE_PATH, APX_HOME } from "#core/config/index.js";
33
- import { callEngine } from "#core/engines/index.js";
34
- import { runSuperAgent, isSuperAgentEnabled } from "#core/agent/super-agent.js";
35
- import { stripThinking } from "#core/util/thinking.js";
36
- import { getRecentTelegramTurnsFromFs, appendGlobalMessage } from "#core/stores/messages.js";
37
- import { compactChannelIfNeeded } from "#core/memory/index.js";
38
- import { readAgents } from "#core/apc/parser.js";
39
- import { buildAgentSystem } from "#core/agent/build-agent-system.js";
40
- import { transcribe as transcribeAudioFile } from "#core/voice/transcription.js";
30
+ // This poller is intentionally thin: per-update logic lives in core/channels/
31
+ // telegram/ dispatch (inbound routing), reply (the super-agent turn),
32
+ // ask-callbacks (the ask_questions flow), inbound/ (media), and the raw Bot API
33
+ // in api.js + media.js. The poller keeps only what the *running process* needs:
34
+ // lifecycle, the poll loop, offset state, and the thin I/O surface (self._send
35
+ // etc.) that the extracted core logic calls back into through `self`.
36
+ import { appendGlobalMessage } from "#core/stores/messages.js";
41
37
  import { resolveAgentName, SUPERAGENT_ACTOR_ID } from "#core/identity/index.js";
42
- import { registerSender, resolveAllowedTools } from "#core/identity/telegram.js";
43
- import { buildRelationshipBlock } from "#core/agent/index.js";
44
- import { getConfirmationStore as getConfirmStore } from "#core/confirmation/pending-store.js";
45
38
  import { CHANNELS } from "#core/constants/channels.js";
46
- import { tryResolveSkillCommand } from "#core/agent/skills/trigger.js";
47
- import { createTelegramConfirmAdapter } from "#core/confirmation/adapters/telegram.js";
48
- import * as askFlow from "#core/channels/telegram/ask.js";
49
-
50
- // API_BASE re-imported from #core/channels/telegram/media.js below
51
- const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
52
-
53
- // All non-class-bound channel logic lives in core/channels/telegram/ — this
54
- // file stays focused on the poller class + plugin lifecycle wiring.
55
39
  import {
56
- buildTelegramMeta,
57
40
  loadState,
58
41
  saveState,
59
42
  resolveBotToken,
@@ -63,11 +46,13 @@ import {
63
46
  sleep,
64
47
  } from "#core/channels/telegram/helpers.js";
65
48
  import { handleUpdate } from "#core/channels/telegram/dispatch.js";
66
-
67
- // ---------- media sending helpers (re-exports) ------------------------------
68
- import { sendPhoto, sendVoice, sendDocument, sendAudio, downloadTelegramFile, API_BASE } from "#core/channels/telegram/media.js";
49
+ import { handleCallbackQuery, startAskFlow, maybeConsumeAskTextAnswer } from "#core/channels/telegram/ask-callbacks.js";
50
+ import { sendMessage, sendChatAction, editMessageReplyMarkup, answerCallbackQuery, getUpdates } from "#core/channels/telegram/api.js";
51
+ import { sendPhoto, sendVoice, sendDocument, sendAudio } from "#core/channels/telegram/media.js";
69
52
  export { sendPhoto, sendVoice, sendDocument, sendAudio };
70
53
 
54
+ const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
55
+
71
56
  // ---------- per-channel poller ----------------------------------------------
72
57
 
73
58
  class ChannelPoller {
@@ -165,13 +150,7 @@ class ChannelPoller {
165
150
  }
166
151
 
167
152
  async _getUpdates() {
168
- const token = resolveBotToken(this.channel);
169
- const url = `${API_BASE}/bot${token}/getUpdates?timeout=25&offset=${this.offset}`;
170
- const res = await fetch(url);
171
- if (!res.ok) throw new Error(`getUpdates ${res.status}`);
172
- const json = await res.json();
173
- if (!json.ok) throw new Error(json.description || "telegram error");
174
- return json.result || [];
153
+ return getUpdates(resolveBotToken(this.channel), { offset: this.offset });
175
154
  }
176
155
 
177
156
  // Method body lives in ./dispatch.js as `handleUpdate(self, u)` so this file
@@ -181,270 +160,42 @@ class ChannelPoller {
181
160
  return handleUpdate(this, u);
182
161
  }
183
162
 
163
+ // ── ask_questions flow ──────────────────────────────────────────────────
164
+ // Orchestration lives in ./ask-callbacks.js (state machine in ./ask.js). These
165
+ // are thin delegates: dispatch.js reaches _startAskFlow / _maybeConsumeAsk...
166
+ // through `self`, and inbound callback_query routes through _handleCallbackQuery.
167
+ // The core functions call back into this poller's I/O surface (_send etc.).
184
168
  async _handleCallbackQuery(callbackQuery) {
185
- // Route ask_questions button presses before the confirmation adapter —
186
- // both use `apx:<verb>:...` namespacing but ask owns its own state.
187
- const data = callbackQuery.data || "";
188
- if (data.startsWith("apx:ask:")) {
189
- await this._handleAskCallback(callbackQuery);
190
- return;
191
- }
192
-
193
- const adapter = createTelegramConfirmAdapter({
194
- token: resolveBotToken(this.channel),
195
- chatId: callbackQuery.message?.chat?.id,
196
- pendingStore: getConfirmStore(),
197
- });
198
- const handled = await adapter.handleCallbackQuery(callbackQuery);
199
- if (!handled) {
200
- this.log(`telegram[${this.channel.name}] unhandled callback_query: ${callbackQuery.data}`);
201
- }
169
+ return handleCallbackQuery(this, callbackQuery);
202
170
  }
203
171
 
204
- // ── ask_questions: state-machine helpers ───────────────────────────────
205
- // The flow lives in telegram-ask.js; this class owns the I/O (sending
206
- // messages, editing keyboards, re-entering the super-agent loop with the
207
- // compiled answer once the flow finishes).
208
-
209
- async _renderQuestion(state) {
210
- const text = askFlow.formatQuestionText(state);
211
- const reply_markup = askFlow.buildKeyboard(state);
212
- // If we already have a message for the previous question, leave its
213
- // keyboard wiped — we draw a fresh message per question for clearer
214
- // history in the chat (the question text stays as a record).
215
- if (state.messageId) {
216
- try {
217
- await this._editKeyboard({
218
- chat_id: state.chatId,
219
- message_id: state.messageId,
220
- reply_markup: { inline_keyboard: [] },
221
- });
222
- } catch { /* best-effort */ }
223
- }
224
- const sent = await this._send({
225
- chat_id: state.chatId,
226
- text,
227
- reply_markup,
228
- parse_mode: "Markdown",
229
- });
230
- state.messageId = sent?.message_id || null;
231
- askFlow.saveState(state.chatId, state);
232
- }
233
-
234
- // Kick off a brand-new ask flow after the super-agent called ask_questions.
235
- // The flow's `resume` callback captures the per-turn context (sender,
236
- // relationship, project) so when the compiled answer arrives we can run
237
- // another super-agent turn without retyping all the inputs.
238
172
  async _startAskFlow(ctx) {
239
- const state = askFlow.startFlow({
240
- chatId: ctx.chat_id,
241
- projectId: ctx.projectId,
242
- authorId: ctx.authorId,
243
- questions: ctx.questions,
244
- resume: async (compiled) => {
245
- await this._runResumedTurn({ ...ctx, compiled });
246
- },
247
- });
248
- await this._renderQuestion(state);
173
+ return startAskFlow(this, ctx);
249
174
  }
250
175
 
251
- // Apply an inline-keyboard press, then react: redraw, advance, or finish.
252
- async _handleAskCallback(callbackQuery) {
253
- const chatId = callbackQuery.message?.chat?.id;
254
- if (!chatId) return;
255
- const result = askFlow.applyCallback(chatId, callbackQuery.data || "");
256
- // Ack the press regardless — keeps the spinner from hanging client-side.
257
- await this._answerCallback({ callback_query_id: callbackQuery.id });
258
- if (!result) return; // stale or unknown — adapter already ack'd.
259
-
260
- if (result.action === "redraw") {
261
- // Multi-select toggle: just refresh the keyboard on the SAME message.
262
- try {
263
- await this._editKeyboard({
264
- chat_id: chatId,
265
- message_id: callbackQuery.message?.message_id,
266
- reply_markup: askFlow.buildKeyboard(result.state),
267
- });
268
- } catch (e) {
269
- this.log(`telegram[${this.channel.name}] redraw failed: ${e.message}`);
270
- }
271
- return;
272
- }
273
- if (result.action === "advance") {
274
- await this._renderQuestion(result.state);
275
- return;
276
- }
277
- if (result.action === "cancel") {
278
- try {
279
- await this._editKeyboard({
280
- chat_id: chatId,
281
- message_id: callbackQuery.message?.message_id,
282
- reply_markup: { inline_keyboard: [] },
283
- });
284
- await this._send({ chat_id: chatId, text: "Pregunta cancelada." });
285
- } catch { /* best-effort */ }
286
- return;
287
- }
288
- if (result.action === "done") {
289
- try {
290
- await this._editKeyboard({
291
- chat_id: chatId,
292
- message_id: callbackQuery.message?.message_id,
293
- reply_markup: { inline_keyboard: [] },
294
- });
295
- } catch { /* best-effort */ }
296
- // Feed the compiled answer back as a synthetic user turn.
297
- if (typeof result.state.resume === "function") {
298
- await result.state.resume(result.compiled);
299
- }
300
- }
176
+ async _maybeConsumeAskTextAnswer(args) {
177
+ return maybeConsumeAskTextAnswer(this, args);
301
178
  }
302
179
 
303
- // Apply a free-text user reply when there's a pending free-text question.
304
- // Returns true iff the message was consumed by the ask flow (so the normal
305
- // super-agent path should be skipped for this update).
306
- async _maybeConsumeAskTextAnswer({ chat_id, text }) {
307
- if (!chat_id || !text) return false;
308
- if (!askFlow.hasPendingFreeText(chat_id)) return false;
309
- const state = askFlow.applyTextAnswer(chat_id, text);
310
- if (!state) return false;
311
- // Advance: emit a synthetic "next" to move past this question.
312
- const next = askFlow.applyCallback(
313
- chat_id,
314
- `apx:ask:${state.correlationId}:next`,
315
- );
316
- if (!next) return true;
317
- if (next.action === "advance") {
318
- await this._renderQuestion(next.state);
319
- return true;
320
- }
321
- if (next.action === "done") {
322
- if (typeof next.state.resume === "function") {
323
- await next.state.resume(next.compiled);
324
- }
325
- return true;
326
- }
327
- return true;
328
- }
329
-
330
- // Run a follow-up super-agent turn with the compiled answers as the user
331
- // prompt. Mirrors the post-runSuperAgent reply path in _handleUpdate but
332
- // skipped of the photo/audio/reset preamble. Re-enters the ask flow if the
333
- // model decides to ask again.
334
- async _runResumedTurn(ctx) {
335
- const { chat_id, compiled, target, relationshipBlock, allowedTools, author, agentDisplay, update_id, sender, authorId } = ctx;
336
- if (!chat_id) return;
337
- // Log the synthetic user message so getRecentTelegramTurnsFromFs picks
338
- // it up on the NEXT inbound. Mirrors how a normal text reply would be
339
- // recorded.
340
- appendGlobalMessage({
341
- channel: CHANNELS.TELEGRAM,
342
- direction: "in",
343
- type: "user",
344
- actor_id: authorId ? String(authorId) : (author || "ask_flow"),
345
- external_id: `ask-${Date.now()}`,
346
- author: author || "user",
347
- body: compiled,
348
- meta: {
349
- chat_id,
350
- user_id: authorId || null,
351
- tg_channel: this.channel.name,
352
- ask_flow: true,
353
- },
354
- });
355
-
356
- const previousMessages = getRecentTelegramTurnsFromFs({
357
- chat_id,
358
- keepRecent: 40,
359
- max_age_hours: 24,
360
- });
361
-
362
- const stopTyping = this._startTyping(chat_id);
363
- try {
364
- const sa = await runSuperAgent({
365
- globalConfig: this.globalConfig,
366
- projects: this.projects,
367
- plugins: this.plugins,
368
- registries: this.registries,
369
- prompt: compiled,
370
- previousMessages,
371
- channel: CHANNELS.TELEGRAM,
372
- relationshipBlock,
373
- allowedTools,
374
- channelMeta: { channel: CHANNELS.TELEGRAM, chat_id, author, route_to_agent: this.channel.route_to_agent },
375
- });
376
- stopTyping();
377
-
378
- // Did the model ask again? Restart the flow instead of replying.
379
- const followupAsk = askFlow.extractAskQuestionsFromTrace(sa.trace);
380
- if (followupAsk) {
381
- await this._startAskFlow({
382
- chat_id,
383
- projectId: target?.id,
384
- authorId,
385
- questions: followupAsk,
386
- author,
387
- agentDisplay,
388
- relationshipBlock,
389
- allowedTools,
390
- target,
391
- sender,
392
- update_id,
393
- });
394
- return;
395
- }
396
-
397
- const replyText = sa.text ? stripThinking(sa.text).trim() : "";
398
- if (replyText) {
399
- await this._send({ chat_id, text: replyText });
400
- appendGlobalMessage({
401
- channel: CHANNELS.TELEGRAM,
402
- direction: "out",
403
- type: "agent",
404
- actor_id: SUPERAGENT_ACTOR_ID,
405
- actor_kind: "superagent",
406
- agent_slug: SUPERAGENT_ACTOR_ID,
407
- author: sa.name || agentDisplay,
408
- body: replyText,
409
- meta: {
410
- chat_id,
411
- tg_channel: this.channel.name,
412
- in_reply_to: update_id,
413
- final: true,
414
- ask_resume: true,
415
- ...(sa.usage ? { usage: sa.usage } : {}),
416
- },
417
- });
418
- }
419
- } catch (e) {
420
- stopTyping();
421
- this.log(`telegram[${this.channel.name}] ask resume failed: ${e.message}`);
422
- try {
423
- await this._send({ chat_id, text: `⚠️ Error procesando tus respuestas (${e.message}).` });
424
- } catch { /* best-effort */ }
425
- }
180
+ // Resolve the bot token + outbound chat for this channel — the single place
181
+ // the "no token / no chat" guards live, shared by every send method.
182
+ _resolve(chat_id) {
183
+ const token = resolveBotToken(this.channel);
184
+ if (!token) throw new Error(`channel ${this.channel.name}: no bot_token`);
185
+ const target = chat_id || resolveChatId(this.channel);
186
+ if (!target) throw new Error(`channel ${this.channel.name}: no chat_id`);
187
+ return { token, target };
426
188
  }
427
189
 
428
- // Show "typing..." indicator in the chat. Telegram clears it automatically
429
- // after 5 seconds, so call this every ~4s while a long operation is going.
190
+ // Show "typing..." indicator. Telegram clears it after ~5s; _startTyping
191
+ // re-pings every 4s. Best-effort failures aren't worth surfacing.
430
192
  async _typing(chat_id) {
431
- try {
432
- const token = resolveBotToken(this.channel);
433
- if (!token || !chat_id) return;
434
- const url = `${API_BASE}/bot${token}/sendChatAction`;
435
- await fetch(url, {
436
- method: "POST",
437
- headers: { "content-type": "application/json" },
438
- body: JSON.stringify({ chat_id, action: "typing" }),
439
- });
440
- } catch {
441
- // best-effort; failures here aren't worth surfacing
442
- }
193
+ const token = resolveBotToken(this.channel);
194
+ if (!token || !chat_id) return;
195
+ try { await sendChatAction(token, chat_id); } catch { /* best-effort */ }
443
196
  }
444
197
 
445
- // Returns a function that pings sendChatAction every 4s until called as
446
- // stop(). Used to wrap the engine round-trip in a "typing" loop so the
447
- // user sees feedback while qwen thinks.
198
+ // Returns a stop() fn; pings the typing indicator every 4s until called.
448
199
  _startTyping(chat_id) {
449
200
  if (!chat_id) return () => {};
450
201
  let stopped = false;
@@ -458,58 +209,27 @@ class ChannelPoller {
458
209
  }
459
210
 
460
211
  async _send({ chat_id, text, reply_markup, parse_mode }) {
461
- const token = resolveBotToken(this.channel);
462
- if (!token) throw new Error(`channel ${this.channel.name}: no bot_token`);
463
- const target = chat_id || resolveChatId(this.channel);
464
- if (!target) throw new Error(`channel ${this.channel.name}: no chat_id`);
465
- const url = `${API_BASE}/bot${token}/sendMessage`;
466
- const body = { chat_id: target, text };
467
- if (reply_markup) body.reply_markup = reply_markup;
468
- if (parse_mode) body.parse_mode = parse_mode;
469
- const res = await fetch(url, {
470
- method: "POST",
471
- headers: { "content-type": "application/json" },
472
- body: JSON.stringify(body),
473
- });
474
- const json = await res.json();
475
- if (!json.ok) throw new Error(json.description || `send failed (${res.status})`);
476
- return json.result;
212
+ const { token, target } = this._resolve(chat_id);
213
+ return sendMessage(token, target, { text, reply_markup, parse_mode });
477
214
  }
478
215
 
479
- // Replace just the inline keyboard on a previously-sent message (used to
480
- // refresh after a multi-select toggle, or to wipe buttons once the flow
481
- // has moved on). Best-effort: failures are logged but don't break the flow.
216
+ // Replace/clear the inline keyboard on a sent message. Best-effort: logged.
482
217
  async _editKeyboard({ chat_id, message_id, reply_markup }) {
483
218
  const token = resolveBotToken(this.channel);
484
219
  if (!token) return;
485
220
  try {
486
- const url = `${API_BASE}/bot${token}/editMessageReplyMarkup`;
487
- const body = { chat_id, message_id };
488
- if (reply_markup) body.reply_markup = reply_markup;
489
- await fetch(url, {
490
- method: "POST",
491
- headers: { "content-type": "application/json" },
492
- body: JSON.stringify(body),
493
- });
221
+ await editMessageReplyMarkup(token, chat_id, message_id, reply_markup);
494
222
  } catch (e) {
495
223
  this.log(`telegram[${this.channel.name}] editMessageReplyMarkup failed: ${e.message}`);
496
224
  }
497
225
  }
498
226
 
499
- // Acknowledge a callback button press so the user's Telegram client clears
500
- // the spinner on the tapped button. Optional `text` shows a small toast.
227
+ // Ack a callback button press so the client clears the spinner (+ optional toast).
501
228
  async _answerCallback({ callback_query_id, text }) {
502
229
  const token = resolveBotToken(this.channel);
503
230
  if (!token) return;
504
231
  try {
505
- const url = `${API_BASE}/bot${token}/answerCallbackQuery`;
506
- const body = { callback_query_id };
507
- if (text) body.text = text;
508
- await fetch(url, {
509
- method: "POST",
510
- headers: { "content-type": "application/json" },
511
- body: JSON.stringify(body),
512
- });
232
+ await answerCallbackQuery(token, callback_query_id, text);
513
233
  } catch (e) {
514
234
  this.log(`telegram[${this.channel.name}] answerCallbackQuery failed: ${e.message}`);
515
235
  }
@@ -517,40 +237,42 @@ class ChannelPoller {
517
237
 
518
238
  /** Send a photo via this channel */
519
239
  async _sendPhoto({ chat_id, photo, caption, parse_mode }) {
520
- const token = resolveBotToken(this.channel);
521
- if (!token) throw new Error(`channel ${this.channel.name}: no bot_token`);
522
- const target = chat_id || resolveChatId(this.channel);
523
- if (!target) throw new Error(`channel ${this.channel.name}: no chat_id`);
240
+ const { token, target } = this._resolve(chat_id);
524
241
  return sendPhoto(token, target, photo, { caption, parse_mode });
525
242
  }
526
243
 
527
244
  /** Send a voice message via this channel */
528
245
  async _sendVoice({ chat_id, audio, caption, duration }) {
529
- const token = resolveBotToken(this.channel);
530
- if (!token) throw new Error(`channel ${this.channel.name}: no bot_token`);
531
- const target = chat_id || resolveChatId(this.channel);
246
+ const { token, target } = this._resolve(chat_id);
532
247
  return sendVoice(token, target, audio, { caption, duration });
533
248
  }
534
249
 
535
250
  /** Send a document (PDF, zip, etc) via this channel */
536
251
  async _sendDocument({ chat_id, document, caption, filename, mime_type }) {
537
- const token = resolveBotToken(this.channel);
538
- if (!token) throw new Error(`channel ${this.channel.name}: no bot_token`);
539
- const target = chat_id || resolveChatId(this.channel);
252
+ const { token, target } = this._resolve(chat_id);
540
253
  return sendDocument(token, target, document, { caption, filename, mime_type });
541
254
  }
542
255
 
543
256
  /** Send an audio file via this channel */
544
257
  async _sendAudio({ chat_id, audio, caption, title, performer }) {
545
- const token = resolveBotToken(this.channel);
546
- if (!token) throw new Error(`channel ${this.channel.name}: no bot_token`);
547
- const target = chat_id || resolveChatId(this.channel);
258
+ const { token, target } = this._resolve(chat_id);
548
259
  return sendAudio(token, target, audio, { caption, title, performer });
549
260
  }
550
261
  }
551
262
 
552
263
  // ---------- plugin export ---------------------------------------------------
553
264
 
265
+ // Pick the poller to send through: the named channel if given, else the first
266
+ // channel with a usable bot token. Shared by every outbound helper below.
267
+ function pickPoller(pollers, channelName) {
268
+ const p =
269
+ (channelName && pollers.find((pp) => pp.channel.name === channelName)) ||
270
+ pollers.find((pp) => resolveBotToken(pp.channel)) ||
271
+ null;
272
+ if (!p) throw new Error("no telegram channel available");
273
+ return p;
274
+ }
275
+
554
276
  export default {
555
277
  id: "telegram",
556
278
 
@@ -590,11 +312,7 @@ export default {
590
312
  // the outbound on `messages` of the channel's target project so audit
591
313
  // trails are complete.
592
314
  async send({ channel: channelName, chat_id, text, author = resolveAgentName(config), project }) {
593
- const p =
594
- (channelName && pollers.find((pp) => pp.channel.name === channelName)) ||
595
- pollers.find((pp) => resolveBotToken(pp.channel)) ||
596
- null;
597
- if (!p) throw new Error("no telegram channel available");
315
+ const p = pickPoller(pollers, channelName);
598
316
  const result = await p._send({ chat_id, text });
599
317
  appendGlobalMessage({
600
318
  channel: CHANNELS.TELEGRAM,
@@ -620,11 +338,7 @@ export default {
620
338
  * opts: { caption, parse_mode, channel, author }
621
339
  */
622
340
  async sendPhoto({ channel: channelName, chat_id, photo, caption, parse_mode, author = resolveAgentName(config) }) {
623
- const p =
624
- (channelName && pollers.find((pp) => pp.channel.name === channelName)) ||
625
- pollers.find((pp) => resolveBotToken(pp.channel)) ||
626
- null;
627
- if (!p) throw new Error("no telegram channel available");
341
+ const p = pickPoller(pollers, channelName);
628
342
  const result = await p._sendPhoto({ chat_id, photo, caption, parse_mode });
629
343
  appendGlobalMessage({
630
344
  channel: CHANNELS.TELEGRAM,
@@ -644,11 +358,7 @@ export default {
644
358
  * audio: local file path or Buffer
645
359
  */
646
360
  async sendVoice({ channel: channelName, chat_id, audio, caption, duration, author = resolveAgentName(config) }) {
647
- const p =
648
- (channelName && pollers.find((pp) => pp.channel.name === channelName)) ||
649
- pollers.find((pp) => resolveBotToken(pp.channel)) ||
650
- null;
651
- if (!p) throw new Error("no telegram channel available");
361
+ const p = pickPoller(pollers, channelName);
652
362
  const result = await p._sendVoice({ chat_id, audio, caption, duration });
653
363
  appendGlobalMessage({
654
364
  channel: CHANNELS.TELEGRAM,
@@ -668,11 +378,7 @@ export default {
668
378
  * document: local file path, Buffer, or public https URL.
669
379
  */
670
380
  async sendDocument({ channel: channelName, chat_id, document, caption, filename, mime_type, author = resolveAgentName(config) }) {
671
- const p =
672
- (channelName && pollers.find((pp) => pp.channel.name === channelName)) ||
673
- pollers.find((pp) => resolveBotToken(pp.channel)) ||
674
- null;
675
- if (!p) throw new Error("no telegram channel available");
381
+ const p = pickPoller(pollers, channelName);
676
382
  const result = await p._sendDocument({ chat_id, document, caption, filename, mime_type });
677
383
  appendGlobalMessage({
678
384
  channel: CHANNELS.TELEGRAM,
@@ -692,11 +398,7 @@ export default {
692
398
  * audio: local file path or Buffer
693
399
  */
694
400
  async sendAudio({ channel: channelName, chat_id, audio, caption, title, performer, author = resolveAgentName(config) }) {
695
- const p =
696
- (channelName && pollers.find((pp) => pp.channel.name === channelName)) ||
697
- pollers.find((pp) => resolveBotToken(pp.channel)) ||
698
- null;
699
- if (!p) throw new Error("no telegram channel available");
401
+ const p = pickPoller(pollers, channelName);
700
402
  const result = await p._sendAudio({ chat_id, audio, caption, title, performer });
701
403
  appendGlobalMessage({
702
404
  channel: CHANNELS.TELEGRAM,
@@ -3327,9 +3327,9 @@
3327
3327
  }
3328
3328
  },
3329
3329
  "node_modules/postcss": {
3330
- "version": "8.5.15",
3331
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
3332
- "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
3330
+ "version": "8.5.16",
3331
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
3332
+ "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
3333
3333
  "funding": [
3334
3334
  {
3335
3335
  "type": "opencollective",