@moxxy/plugin-telegram 0.27.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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/dist/approval.d.ts +37 -0
  3. package/dist/approval.d.ts.map +1 -0
  4. package/dist/approval.js +75 -0
  5. package/dist/approval.js.map +1 -0
  6. package/dist/channel/approval-prompt.d.ts +20 -0
  7. package/dist/channel/approval-prompt.d.ts.map +1 -0
  8. package/dist/channel/approval-prompt.js +34 -0
  9. package/dist/channel/approval-prompt.js.map +1 -0
  10. package/dist/channel/callback-handler.d.ts +32 -0
  11. package/dist/channel/callback-handler.d.ts.map +1 -0
  12. package/dist/channel/callback-handler.js +212 -0
  13. package/dist/channel/callback-handler.js.map +1 -0
  14. package/dist/channel/frame-pump.d.ts +58 -0
  15. package/dist/channel/frame-pump.d.ts.map +1 -0
  16. package/dist/channel/frame-pump.js +159 -0
  17. package/dist/channel/frame-pump.js.map +1 -0
  18. package/dist/channel/html.d.ts +24 -0
  19. package/dist/channel/html.d.ts.map +1 -0
  20. package/dist/channel/html.js +62 -0
  21. package/dist/channel/html.js.map +1 -0
  22. package/dist/channel/pairing-handler.d.ts +66 -0
  23. package/dist/channel/pairing-handler.d.ts.map +1 -0
  24. package/dist/channel/pairing-handler.js +147 -0
  25. package/dist/channel/pairing-handler.js.map +1 -0
  26. package/dist/channel/permission-prompt.d.ts +21 -0
  27. package/dist/channel/permission-prompt.d.ts.map +1 -0
  28. package/dist/channel/permission-prompt.js +36 -0
  29. package/dist/channel/permission-prompt.js.map +1 -0
  30. package/dist/channel/slash-handler.d.ts +47 -0
  31. package/dist/channel/slash-handler.d.ts.map +1 -0
  32. package/dist/channel/slash-handler.js +214 -0
  33. package/dist/channel/slash-handler.js.map +1 -0
  34. package/dist/channel/text-handler.d.ts +43 -0
  35. package/dist/channel/text-handler.d.ts.map +1 -0
  36. package/dist/channel/text-handler.js +116 -0
  37. package/dist/channel/text-handler.js.map +1 -0
  38. package/dist/channel/turn-runner.d.ts +40 -0
  39. package/dist/channel/turn-runner.d.ts.map +1 -0
  40. package/dist/channel/turn-runner.js +67 -0
  41. package/dist/channel/turn-runner.js.map +1 -0
  42. package/dist/channel/typing-indicator.d.ts +14 -0
  43. package/dist/channel/typing-indicator.d.ts.map +1 -0
  44. package/dist/channel/typing-indicator.js +29 -0
  45. package/dist/channel/typing-indicator.js.map +1 -0
  46. package/dist/channel/voice-handler.d.ts +42 -0
  47. package/dist/channel/voice-handler.d.ts.map +1 -0
  48. package/dist/channel/voice-handler.js +138 -0
  49. package/dist/channel/voice-handler.js.map +1 -0
  50. package/dist/channel.d.ts +118 -0
  51. package/dist/channel.d.ts.map +1 -0
  52. package/dist/channel.js +478 -0
  53. package/dist/channel.js.map +1 -0
  54. package/dist/channel.test-d.d.ts +2 -0
  55. package/dist/channel.test-d.d.ts.map +1 -0
  56. package/dist/channel.test-d.js +5 -0
  57. package/dist/channel.test-d.js.map +1 -0
  58. package/dist/format.d.ts +38 -0
  59. package/dist/format.d.ts.map +1 -0
  60. package/dist/format.js +237 -0
  61. package/dist/format.js.map +1 -0
  62. package/dist/index.d.ts +24 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +304 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/keys.d.ts +37 -0
  67. package/dist/keys.d.ts.map +1 -0
  68. package/dist/keys.js +42 -0
  69. package/dist/keys.js.map +1 -0
  70. package/dist/pair-flow.d.ts +18 -0
  71. package/dist/pair-flow.d.ts.map +1 -0
  72. package/dist/pair-flow.js +119 -0
  73. package/dist/pair-flow.js.map +1 -0
  74. package/dist/pairing.d.ts +90 -0
  75. package/dist/pairing.d.ts.map +1 -0
  76. package/dist/pairing.js +97 -0
  77. package/dist/pairing.js.map +1 -0
  78. package/dist/permission.d.ts +25 -0
  79. package/dist/permission.d.ts.map +1 -0
  80. package/dist/permission.js +49 -0
  81. package/dist/permission.js.map +1 -0
  82. package/dist/render.d.ts +68 -0
  83. package/dist/render.d.ts.map +1 -0
  84. package/dist/render.js +439 -0
  85. package/dist/render.js.map +1 -0
  86. package/dist/setup-wizard.d.ts +21 -0
  87. package/dist/setup-wizard.d.ts.map +1 -0
  88. package/dist/setup-wizard.js +140 -0
  89. package/dist/setup-wizard.js.map +1 -0
  90. package/package.json +91 -0
  91. package/src/approval.test.ts +103 -0
  92. package/src/approval.ts +86 -0
  93. package/src/channel/approval-prompt.ts +46 -0
  94. package/src/channel/callback-handler.test.ts +188 -0
  95. package/src/channel/callback-handler.ts +256 -0
  96. package/src/channel/frame-pump.ts +176 -0
  97. package/src/channel/html.test.ts +40 -0
  98. package/src/channel/html.ts +60 -0
  99. package/src/channel/pairing-handler.ts +181 -0
  100. package/src/channel/permission-prompt.ts +51 -0
  101. package/src/channel/slash-handler.ts +259 -0
  102. package/src/channel/text-handler.test.ts +222 -0
  103. package/src/channel/text-handler.ts +174 -0
  104. package/src/channel/turn-runner.ts +102 -0
  105. package/src/channel/typing-indicator.ts +31 -0
  106. package/src/channel/voice-handler.test.ts +328 -0
  107. package/src/channel/voice-handler.ts +198 -0
  108. package/src/channel/voice-reply.test.ts +223 -0
  109. package/src/channel.test-d.ts +7 -0
  110. package/src/channel.ts +564 -0
  111. package/src/format.test.ts +159 -0
  112. package/src/format.ts +257 -0
  113. package/src/index.ts +347 -0
  114. package/src/keys.ts +52 -0
  115. package/src/pair-flow.ts +131 -0
  116. package/src/pairing.test.ts +113 -0
  117. package/src/pairing.ts +184 -0
  118. package/src/permission.test.ts +56 -0
  119. package/src/permission.ts +61 -0
  120. package/src/render.test.ts +350 -0
  121. package/src/render.ts +522 -0
  122. package/src/setup-wizard.ts +178 -0
  123. package/src/subcommands.test.ts +153 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Hard cap on an inbound audio file we will buffer into memory before
3
+ * transcribing. Telegram allows ~20MB uploads via getFile, and the handler
4
+ * accepts arbitrary uploaded `message:audio` (not just short voice notes), so
5
+ * an authorized-but-hostile chat could otherwise push a large file we fully
6
+ * buffer. 20MB matches Telegram's own getFile ceiling.
7
+ */
8
+ const MAX_AUDIO_BYTES = 20 * 1024 * 1024;
9
+ /** Abort the file download if Telegram's CDN hasn't responded in this window. */
10
+ const DOWNLOAD_TIMEOUT_MS = 30_000;
11
+ /**
12
+ * Handle inbound voice notes and uploaded audio files. Authorization
13
+ * gate matches the text path. If no Transcriber is registered on the
14
+ * session, reply with a one-time guidance message instead of silently
15
+ * swallowing the audio. Otherwise: download via Bot-API file URL,
16
+ * transcribe through the active Transcriber, echo a short "heard:"
17
+ * confirmation, then run a normal user turn with the transcript.
18
+ */
19
+ export async function handleVoiceMessage(ctx, state, deps, cb) {
20
+ const chatId = ctx.chat?.id;
21
+ if (!chatId)
22
+ return;
23
+ // Pull whichever audio variant Telegram delivered. `voice` is a press-
24
+ // and-hold voice note; `audio` is an uploaded audio file. Both expose
25
+ // a `file_id` and (for voice) a mime_type — we trust voice's
26
+ // `audio/ogg` default when missing.
27
+ const voice = ctx.message?.voice;
28
+ const audio = ctx.message?.audio;
29
+ const media = voice ?? audio;
30
+ if (!media)
31
+ return;
32
+ if (!deps.pairing.isAuthorized(chatId)) {
33
+ await ctx.reply('This bot is paired with a different chat (or not paired yet). Run `moxxy telegram pair` to (re-)pair.');
34
+ return;
35
+ }
36
+ if (state.busy) {
37
+ await ctx.reply('I am still working on the previous prompt. Send /cancel to abort it.');
38
+ return;
39
+ }
40
+ if (!state.session) {
41
+ await ctx.reply('Session is not ready yet.');
42
+ return;
43
+ }
44
+ const transcriber = state.session.transcribers.tryGetActive();
45
+ if (!transcriber) {
46
+ await ctx.reply("Heard a voice note, but no speech-to-text backend is configured. Install @moxxy/plugin-stt-whisper and run `moxxy login openai` (or set OPENAI_API_KEY) to enable voice input.");
47
+ return;
48
+ }
49
+ // Reject oversized uploads up-front using the size Telegram reports on the
50
+ // voice/audio object, before we spend a download or any memory on it.
51
+ const declaredSize = media.file_size;
52
+ if (typeof declaredSize === 'number' && declaredSize > MAX_AUDIO_BYTES) {
53
+ await ctx.reply(`That audio is too large (${Math.round(declaredSize / (1024 * 1024))}MB). The limit is ${MAX_AUDIO_BYTES / (1024 * 1024)}MB.`);
54
+ return;
55
+ }
56
+ let fileInfo;
57
+ try {
58
+ fileInfo = await ctx.api.getFile(media.file_id);
59
+ }
60
+ catch (err) {
61
+ deps.logger?.warn('telegram getFile failed', {
62
+ err: err instanceof Error ? err.message : String(err),
63
+ });
64
+ await ctx.reply('Could not look up that voice note with Telegram.');
65
+ return;
66
+ }
67
+ if (!fileInfo.file_path) {
68
+ await ctx.reply('Telegram did not return a downloadable file path for that voice note.');
69
+ return;
70
+ }
71
+ const url = `https://api.telegram.org/file/bot${deps.token}/${fileInfo.file_path}`;
72
+ const fetcher = cb.fetchAudio ?? ((u, init) => fetch(u, init));
73
+ // Bound the download with an AbortSignal timeout so a hung/slow Telegram CDN
74
+ // connection can't block the handler indefinitely.
75
+ const ac = new AbortController();
76
+ const timer = setTimeout(() => ac.abort(), DOWNLOAD_TIMEOUT_MS);
77
+ let bytes;
78
+ try {
79
+ const response = await fetcher(url, { signal: ac.signal });
80
+ if (!response.ok) {
81
+ deps.logger?.warn('telegram voice download failed', {
82
+ ...(response.status !== undefined ? { status: response.status } : {}),
83
+ ...(response.statusText ? { statusText: response.statusText } : {}),
84
+ });
85
+ await ctx.reply('Failed to download the voice note from Telegram.');
86
+ return;
87
+ }
88
+ // Trust the Content-Length header (when present) to bail before buffering a
89
+ // body that lies about its size on the voice/audio object.
90
+ const declaredLen = Number(response.headers?.get('content-length') ?? '');
91
+ if (Number.isFinite(declaredLen) && declaredLen > MAX_AUDIO_BYTES) {
92
+ await ctx.reply('That audio is too large to transcribe.');
93
+ return;
94
+ }
95
+ const buf = await response.arrayBuffer();
96
+ if (buf.byteLength > MAX_AUDIO_BYTES) {
97
+ await ctx.reply('That audio is too large to transcribe.');
98
+ return;
99
+ }
100
+ bytes = new Uint8Array(buf);
101
+ }
102
+ catch (err) {
103
+ deps.logger?.warn('telegram voice fetch failed', {
104
+ err: err instanceof Error ? err.message : String(err),
105
+ });
106
+ await ctx.reply(ac.signal.aborted
107
+ ? 'Downloading the voice note timed out.'
108
+ : 'Failed to download the voice note from Telegram.');
109
+ return;
110
+ }
111
+ finally {
112
+ clearTimeout(timer);
113
+ }
114
+ // Voice notes are OGG/Opus by default; uploaded audio carries its own mime.
115
+ const mimeType = media.mime_type ?? (voice ? 'audio/ogg' : 'audio/mpeg');
116
+ let transcript;
117
+ try {
118
+ const result = await transcriber.transcribe(bytes, { mimeType });
119
+ transcript = result.text.trim();
120
+ }
121
+ catch (err) {
122
+ deps.logger?.warn('telegram voice transcription failed', {
123
+ err: err instanceof Error ? err.message : String(err),
124
+ });
125
+ await ctx.reply(`Transcription failed: ${err instanceof Error ? err.message : String(err)}`);
126
+ return;
127
+ }
128
+ if (!transcript) {
129
+ await ctx.reply('Could not transcribe the voice note (got empty text).');
130
+ return;
131
+ }
132
+ // Echo what we heard so the user can spot misrecognitions before the
133
+ // agent acts on it. Italics keep it visually distinct from a normal
134
+ // reply.
135
+ await ctx.reply(`_heard:_ ${transcript}`, { parse_mode: 'Markdown' });
136
+ await cb.runUserTurn(ctx, chatId, transcript);
137
+ }
138
+ //# sourceMappingURL=voice-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-handler.js","sourceRoot":"","sources":["../../src/channel/voice-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import type { ClientSession as Session } from '@moxxy/sdk';
2
+ import type { Channel, ChannelHandle, ChannelStartOptsBase } from '@moxxy/sdk';
3
+ import type { VaultStore } from '@moxxy/plugin-vault';
4
+ import { TelegramPermissionResolver } from './permission.js';
5
+ import { TelegramApprovalResolver } from './approval.js';
6
+ import { PairingHandler } from './channel/pairing-handler.js';
7
+ export type { PairingConfirmResult } from './channel/pairing-handler.js';
8
+ export interface TelegramStartOpts extends ChannelStartOptsBase {
9
+ readonly session: Session;
10
+ /**
11
+ * If true (and no chat is paired yet), open a host-issued QR pairing window on
12
+ * startup: mint a code, publish a `t.me/<bot>?start=<code>` deep link as the
13
+ * connect value, and pair whichever chat presents that code back (deep-link tap
14
+ * or a plain 6-digit message). Set by the `moxxy channels telegram pair` command
15
+ * (which renders the deep link as a terminal QR and waits via `onPaired`).
16
+ */
17
+ readonly pair?: boolean;
18
+ /**
19
+ * The channel is running on its own dedicated runner under a GUI control
20
+ * surface (the desktop Channels panel) rather than a terminal. Equivalent to
21
+ * `pair` for the unpaired case — `start()` opens the same host-issued QR
22
+ * pairing window instead of throwing — so the desktop pairs with the identical
23
+ * mechanism. Threaded by the CLI from `ChannelDef.dedicatedRunner` /
24
+ * `--dedicated` / `MOXXY_DEDICATED_RUNNER`.
25
+ */
26
+ readonly dedicated?: boolean;
27
+ }
28
+ export interface TelegramChannelOptions {
29
+ readonly vault: VaultStore;
30
+ readonly token?: string;
31
+ readonly logger?: {
32
+ info(msg: string, meta?: Record<string, unknown>): void;
33
+ warn(msg: string, meta?: Record<string, unknown>): void;
34
+ };
35
+ readonly editFrameMs?: number;
36
+ }
37
+ export declare class TelegramChannel implements Channel<TelegramStartOpts> {
38
+ readonly name = "telegram";
39
+ readonly permissionResolver: TelegramPermissionResolver;
40
+ readonly approvalResolver: TelegramApprovalResolver;
41
+ private readonly opts;
42
+ private bot;
43
+ private botLink;
44
+ private hostPairingCode;
45
+ private readonly connectListeners;
46
+ private currentChatId;
47
+ private lastChatId;
48
+ private logUnsub;
49
+ private session;
50
+ private model;
51
+ private activeModelOverride;
52
+ private yolo;
53
+ private voiceReplies;
54
+ private readonly turns;
55
+ private awaitingApprovalText;
56
+ private handle;
57
+ private readonly framePump;
58
+ private readonly typing;
59
+ private readonly pairing;
60
+ constructor(opts: TelegramChannelOptions);
61
+ /** This channel's connect value (see {@link Channel.requestUrl}): the resolved
62
+ * bot's `t.me` link, surfaced by control surfaces as a QR / open-bot step.
63
+ * While a host pairing window is open it includes the `?start=<code>` payload. */
64
+ get requestUrl(): string | null;
65
+ /** Whether the "connect the other side" step is satisfied — i.e. a chat is
66
+ * paired (see {@link Channel.connected}). A host shows "✓ Connected" instead
67
+ * of the pairing QR once this is true. */
68
+ get connected(): boolean;
69
+ private notifyConnectChange;
70
+ start(startOpts: TelegramStartOpts): Promise<ChannelHandle>;
71
+ pairingPhase(): ReturnType<PairingHandler['phase']>;
72
+ unpair(): void;
73
+ /**
74
+ * Subscribe to "a chat just paired" — fires once when the host-issued QR
75
+ * pairing completes. The `moxxy channels telegram pair` command uses this to
76
+ * print success and stop waiting; returns an unsubscribe function.
77
+ */
78
+ onPaired(listener: (chatId: number) => void): () => void;
79
+ private handleVoice;
80
+ private handleText;
81
+ /** Persist + apply the voice-replies preference (the `/voice` toggle). */
82
+ private setVoiceReplies;
83
+ /**
84
+ * Speak the final assistant reply as a voice note, when enabled. Best-effort
85
+ * and fully isolated (never throws): synthesize via the session's active
86
+ * Synthesizer, transcode to OGG/Opus (or send plain audio when ffmpeg is
87
+ * unavailable), and deliver via grammy. The text reply already went out.
88
+ */
89
+ private sendVoiceReply;
90
+ /**
91
+ * Plain-message fallback for host-issued pairing: if a host window is open and
92
+ * an unauthorized chat sends exactly the 6-digit code, pair it (covers clients
93
+ * that don't auto-deliver the `?start=` deep-link payload). Returns true when
94
+ * the message was a pairing attempt we handled (paired, or a wrong-code reply)
95
+ * so the caller doesn't also emit the generic "not paired" rejection.
96
+ */
97
+ private tryHostPair;
98
+ /**
99
+ * Run a handler promise detached from the grammy poll loop. grammy awaits the
100
+ * value a middleware returns before fetching the next update batch; returning
101
+ * void here (instead of the handler promise) lets the loop keep delivering
102
+ * callback_query / `/cancel` while a turn runs. Errors are logged here because
103
+ * `bot.catch` only sees rejections of the AWAITED middleware chain.
104
+ */
105
+ private dispatchInBackground;
106
+ private runUserTurn;
107
+ /**
108
+ * Post the assistant's prose for a turn this channel did not initiate. Gated
109
+ * by `!busy` (our own turns are rendered by the FramePump from the runUserTurn
110
+ * iterator) and by having served a chat at least once. Sent as plain text to
111
+ * avoid parse-mode pitfalls; the view itself lives on the web surface.
112
+ */
113
+ private mirrorForeignTurn;
114
+ private askForPermission;
115
+ private askForApproval;
116
+ private dispatchCallback;
117
+ }
118
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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"}