@sympoies/dsh-telegram 0.0.0-bootstrap.0 → 0.6.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 (194) hide show
  1. package/LICENSE +1 -2
  2. package/NOTICE +19 -0
  3. package/README.md +589 -2
  4. package/cordis.patch.yml +14 -0
  5. package/lib/access.d.ts +76 -0
  6. package/lib/access.d.ts.map +1 -0
  7. package/lib/access.js +137 -0
  8. package/lib/access.js.map +1 -0
  9. package/lib/client.js +835 -0
  10. package/lib/commands.d.ts +51 -0
  11. package/lib/commands.d.ts.map +1 -0
  12. package/lib/commands.js +83 -0
  13. package/lib/commands.js.map +1 -0
  14. package/lib/config.d.ts +125 -0
  15. package/lib/config.d.ts.map +1 -0
  16. package/lib/config.js +97 -0
  17. package/lib/config.js.map +1 -0
  18. package/lib/diagnostics.d.ts +58 -0
  19. package/lib/diagnostics.d.ts.map +1 -0
  20. package/lib/diagnostics.js +59 -0
  21. package/lib/diagnostics.js.map +1 -0
  22. package/lib/failures.d.ts +71 -0
  23. package/lib/failures.d.ts.map +1 -0
  24. package/lib/failures.js +139 -0
  25. package/lib/failures.js.map +1 -0
  26. package/lib/harness/host.d.ts +106 -0
  27. package/lib/harness/host.d.ts.map +1 -0
  28. package/lib/harness/host.js +142 -0
  29. package/lib/harness/host.js.map +1 -0
  30. package/lib/harness/message.d.ts +45 -0
  31. package/lib/harness/message.d.ts.map +1 -0
  32. package/lib/harness/message.js +28 -0
  33. package/lib/harness/message.js.map +1 -0
  34. package/lib/harness/model-selection.d.ts +82 -0
  35. package/lib/harness/model-selection.d.ts.map +1 -0
  36. package/lib/harness/model-selection.js +103 -0
  37. package/lib/harness/model-selection.js.map +1 -0
  38. package/lib/harness/questions-seam.d.ts +34 -0
  39. package/lib/harness/questions-seam.d.ts.map +1 -0
  40. package/lib/harness/questions-seam.js +45 -0
  41. package/lib/harness/questions-seam.js.map +1 -0
  42. package/lib/harness/types.d.ts +108 -0
  43. package/lib/harness/types.d.ts.map +1 -0
  44. package/lib/harness/types.js +23 -0
  45. package/lib/harness/types.js.map +1 -0
  46. package/lib/index.d.ts +65 -0
  47. package/lib/index.d.ts.map +1 -0
  48. package/lib/index.js +977 -0
  49. package/lib/index.js.map +1 -0
  50. package/lib/interact/approvals.d.ts +59 -0
  51. package/lib/interact/approvals.d.ts.map +1 -0
  52. package/lib/interact/approvals.js +132 -0
  53. package/lib/interact/approvals.js.map +1 -0
  54. package/lib/interact/pending.d.ts +56 -0
  55. package/lib/interact/pending.d.ts.map +1 -0
  56. package/lib/interact/pending.js +89 -0
  57. package/lib/interact/pending.js.map +1 -0
  58. package/lib/interact/questions.d.ts +100 -0
  59. package/lib/interact/questions.d.ts.map +1 -0
  60. package/lib/interact/questions.js +255 -0
  61. package/lib/interact/questions.js.map +1 -0
  62. package/lib/interact/surface.d.ts +33 -0
  63. package/lib/interact/surface.d.ts.map +1 -0
  64. package/lib/interact/surface.js +39 -0
  65. package/lib/interact/surface.js.map +1 -0
  66. package/lib/interact/text-capture.d.ts +38 -0
  67. package/lib/interact/text-capture.d.ts.map +1 -0
  68. package/lib/interact/text-capture.js +75 -0
  69. package/lib/interact/text-capture.js.map +1 -0
  70. package/lib/media/collect.d.ts +157 -0
  71. package/lib/media/collect.d.ts.map +1 -0
  72. package/lib/media/collect.js +235 -0
  73. package/lib/media/collect.js.map +1 -0
  74. package/lib/media/extractor.d.ts +117 -0
  75. package/lib/media/extractor.d.ts.map +1 -0
  76. package/lib/media/extractor.js +207 -0
  77. package/lib/media/extractor.js.map +1 -0
  78. package/lib/media/intake.d.ts +57 -0
  79. package/lib/media/intake.d.ts.map +1 -0
  80. package/lib/media/intake.js +115 -0
  81. package/lib/media/intake.js.map +1 -0
  82. package/lib/media/limits.d.ts +69 -0
  83. package/lib/media/limits.d.ts.map +1 -0
  84. package/lib/media/limits.js +97 -0
  85. package/lib/media/limits.js.map +1 -0
  86. package/lib/media/ocr.d.ts +66 -0
  87. package/lib/media/ocr.d.ts.map +1 -0
  88. package/lib/media/ocr.js +131 -0
  89. package/lib/media/ocr.js.map +1 -0
  90. package/lib/media/screenshot.d.ts +57 -0
  91. package/lib/media/screenshot.d.ts.map +1 -0
  92. package/lib/media/screenshot.js +101 -0
  93. package/lib/media/screenshot.js.map +1 -0
  94. package/lib/media/vision.d.ts +60 -0
  95. package/lib/media/vision.d.ts.map +1 -0
  96. package/lib/media/vision.js +74 -0
  97. package/lib/media/vision.js.map +1 -0
  98. package/lib/render/clamp.d.ts +34 -0
  99. package/lib/render/clamp.d.ts.map +1 -0
  100. package/lib/render/clamp.js +65 -0
  101. package/lib/render/clamp.js.map +1 -0
  102. package/lib/render/escape.d.ts +15 -0
  103. package/lib/render/escape.d.ts.map +1 -0
  104. package/lib/render/escape.js +17 -0
  105. package/lib/render/escape.js.map +1 -0
  106. package/lib/reply/activity.d.ts +31 -0
  107. package/lib/reply/activity.d.ts.map +1 -0
  108. package/lib/reply/activity.js +88 -0
  109. package/lib/reply/activity.js.map +1 -0
  110. package/lib/reply/rich-stream.d.ts +173 -0
  111. package/lib/reply/rich-stream.d.ts.map +1 -0
  112. package/lib/reply/rich-stream.js +286 -0
  113. package/lib/reply/rich-stream.js.map +1 -0
  114. package/lib/reply/split-markdown.d.ts +21 -0
  115. package/lib/reply/split-markdown.d.ts.map +1 -0
  116. package/lib/reply/split-markdown.js +87 -0
  117. package/lib/reply/split-markdown.js.map +1 -0
  118. package/lib/reply/turn-bridge.d.ts +143 -0
  119. package/lib/reply/turn-bridge.d.ts.map +1 -0
  120. package/lib/reply/turn-bridge.js +182 -0
  121. package/lib/reply/turn-bridge.js.map +1 -0
  122. package/lib/router.d.ts +415 -0
  123. package/lib/router.d.ts.map +1 -0
  124. package/lib/router.js +663 -0
  125. package/lib/router.js.map +1 -0
  126. package/lib/secrets.d.ts +30 -0
  127. package/lib/secrets.d.ts.map +1 -0
  128. package/lib/secrets.js +51 -0
  129. package/lib/secrets.js.map +1 -0
  130. package/lib/session/bindings.d.ts +81 -0
  131. package/lib/session/bindings.d.ts.map +1 -0
  132. package/lib/session/bindings.js +170 -0
  133. package/lib/session/bindings.js.map +1 -0
  134. package/lib/session/history.d.ts +50 -0
  135. package/lib/session/history.d.ts.map +1 -0
  136. package/lib/session/history.js +127 -0
  137. package/lib/session/history.js.map +1 -0
  138. package/lib/session/models.d.ts +99 -0
  139. package/lib/session/models.d.ts.map +1 -0
  140. package/lib/session/models.js +123 -0
  141. package/lib/session/models.js.map +1 -0
  142. package/lib/session/permission.d.ts +83 -0
  143. package/lib/session/permission.d.ts.map +1 -0
  144. package/lib/session/permission.js +107 -0
  145. package/lib/session/permission.js.map +1 -0
  146. package/lib/session/picker.d.ts +49 -0
  147. package/lib/session/picker.d.ts.map +1 -0
  148. package/lib/session/picker.js +107 -0
  149. package/lib/session/picker.js.map +1 -0
  150. package/lib/session/preferences.d.ts +55 -0
  151. package/lib/session/preferences.d.ts.map +1 -0
  152. package/lib/session/preferences.js +102 -0
  153. package/lib/session/preferences.js.map +1 -0
  154. package/lib/session/recovery.d.ts +66 -0
  155. package/lib/session/recovery.d.ts.map +1 -0
  156. package/lib/session/recovery.js +112 -0
  157. package/lib/session/recovery.js.map +1 -0
  158. package/lib/session/runner.d.ts +223 -0
  159. package/lib/session/runner.d.ts.map +1 -0
  160. package/lib/session/runner.js +260 -0
  161. package/lib/session/runner.js.map +1 -0
  162. package/lib/session/workspaces.d.ts +28 -0
  163. package/lib/session/workspaces.d.ts.map +1 -0
  164. package/lib/session/workspaces.js +53 -0
  165. package/lib/session/workspaces.js.map +1 -0
  166. package/lib/telegram/addressing.d.ts +39 -0
  167. package/lib/telegram/addressing.d.ts.map +1 -0
  168. package/lib/telegram/addressing.js +81 -0
  169. package/lib/telegram/addressing.js.map +1 -0
  170. package/lib/telegram/albums.d.ts +46 -0
  171. package/lib/telegram/albums.d.ts.map +1 -0
  172. package/lib/telegram/albums.js +104 -0
  173. package/lib/telegram/albums.js.map +1 -0
  174. package/lib/telegram/api.d.ts +238 -0
  175. package/lib/telegram/api.d.ts.map +1 -0
  176. package/lib/telegram/api.js +518 -0
  177. package/lib/telegram/api.js.map +1 -0
  178. package/lib/telegram/poller.d.ts +59 -0
  179. package/lib/telegram/poller.d.ts.map +1 -0
  180. package/lib/telegram/poller.js +94 -0
  181. package/lib/telegram/poller.js.map +1 -0
  182. package/lib/telegram/types.d.ts +130 -0
  183. package/lib/telegram/types.d.ts.map +1 -0
  184. package/lib/telegram/types.js +9 -0
  185. package/lib/telegram/types.js.map +1 -0
  186. package/lib/telegram/typing.d.ts +47 -0
  187. package/lib/telegram/typing.d.ts.map +1 -0
  188. package/lib/telegram/typing.js +91 -0
  189. package/lib/telegram/typing.js.map +1 -0
  190. package/lib/versions.d.ts +63 -0
  191. package/lib/versions.d.ts.map +1 -0
  192. package/lib/versions.js +135 -0
  193. package/lib/versions.js.map +1 -0
  194. package/package.json +94 -10
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The long-poll receive loop.
3
+ *
4
+ * Telegram's `getUpdates` offset is the delivery guarantee: an update is
5
+ * redelivered until an offset past it is acknowledged. So the offset advances
6
+ * only after the handler for that update returns. A handler that throws leaves
7
+ * the offset where it is and the update comes back on the next poll — which is
8
+ * why the router above it never throws for ordinary failures, and only a real
9
+ * fault gets a retry.
10
+ *
11
+ * Reconnection is the loop's other job. A laptop that sleeps, a network that
12
+ * drops, a Telegram hiccup — each surfaces as a rejected poll, and the bot has
13
+ * to come back on its own rather than going quietly silent.
14
+ */
15
+ import { SILENT_LOGGER } from '../harness/types.js';
16
+ export class UpdatePoller {
17
+ options;
18
+ logger;
19
+ sleep;
20
+ offset = 0;
21
+ connected = false;
22
+ constructor(options) {
23
+ this.options = options;
24
+ this.logger = options.logger ?? SILENT_LOGGER;
25
+ this.sleep = options.sleep ?? defaultSleep;
26
+ }
27
+ /** The next offset that will be acknowledged; exposed for tests and status. */
28
+ get acknowledged() {
29
+ return this.offset;
30
+ }
31
+ /**
32
+ * Poll until the signal aborts.
33
+ *
34
+ * @param signal - stops the loop; an in-flight poll is aborted with it.
35
+ */
36
+ async run(signal) {
37
+ let attempt = 0;
38
+ while (!signal.aborted) {
39
+ try {
40
+ const updates = await this.options.source.getUpdates(this.offset, this.options.longPollSeconds ?? 25, signal);
41
+ attempt = 0;
42
+ if (!this.connected) {
43
+ this.connected = true;
44
+ this.options.onConnected?.();
45
+ }
46
+ await this.dispatch(updates, signal);
47
+ }
48
+ catch (error) {
49
+ if (signal.aborted)
50
+ return;
51
+ this.connected = false;
52
+ attempt += 1;
53
+ const delay = this.backoff(attempt);
54
+ this.logger.warn(`[dsh-telegram] poll failed; retrying in ${delay}ms`, error);
55
+ await this.sleep(delay, signal);
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * Hand each update to the handler, acknowledging only after it returns.
61
+ * A throw stops the batch so the failed update is redelivered.
62
+ */
63
+ async dispatch(updates, signal) {
64
+ for (const update of updates) {
65
+ if (signal.aborted)
66
+ return;
67
+ await this.options.onUpdate(update);
68
+ this.offset = Math.max(this.offset, update.update_id + 1);
69
+ }
70
+ }
71
+ /** Exponential backoff, capped so a long outage still retries regularly. */
72
+ backoff(attempt) {
73
+ const base = this.options.baseDelayMs ?? 1000;
74
+ const max = this.options.maxDelayMs ?? 30_000;
75
+ return Math.min(base * 2 ** Math.min(attempt - 1, 8), max);
76
+ }
77
+ }
78
+ /** Cancellable sleep used between reconnect attempts. */
79
+ function defaultSleep(ms, signal) {
80
+ return new Promise((resolve) => {
81
+ if (signal?.aborted)
82
+ return resolve();
83
+ const timer = setTimeout(() => {
84
+ signal?.removeEventListener('abort', onAbort);
85
+ resolve();
86
+ }, ms);
87
+ const onAbort = () => {
88
+ clearTimeout(timer);
89
+ resolve();
90
+ };
91
+ signal?.addEventListener('abort', onAbort, { once: true });
92
+ });
93
+ }
94
+ //# sourceMappingURL=poller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poller.js","sourceRoot":"","sources":["../../src/telegram/poller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * The slice of the Telegram Bot API this plugin actually speaks.
3
+ *
4
+ * Deliberately partial: every field here is one the adapter reads, so an
5
+ * upstream schema change surfaces as a type error at the exact use site rather
6
+ * than as a silently-undefined value deep in the pipeline.
7
+ */
8
+ /** A button in an inline keyboard. */
9
+ export interface InlineButton {
10
+ /** Label shown on the button. */
11
+ readonly text: string;
12
+ /** Opaque payload echoed back in the callback query (Telegram caps it at 64 bytes). */
13
+ readonly callbackData: string;
14
+ }
15
+ /** Rows of inline buttons; each inner array is one row. */
16
+ export type InlineKeyboard = readonly (readonly InlineButton[])[];
17
+ /** The bot's own account, as returned by `getMe`. */
18
+ export interface BotUser {
19
+ readonly id: number;
20
+ readonly is_bot: boolean;
21
+ readonly first_name?: string;
22
+ readonly username?: string;
23
+ }
24
+ /** A Telegram chat. */
25
+ export interface TelegramChat {
26
+ readonly id: number;
27
+ readonly type: 'private' | 'group' | 'supergroup' | 'channel';
28
+ readonly title?: string;
29
+ readonly username?: string;
30
+ }
31
+ /** A Telegram user. */
32
+ export interface TelegramUser {
33
+ readonly id: number;
34
+ readonly is_bot?: boolean;
35
+ readonly first_name?: string;
36
+ readonly username?: string;
37
+ }
38
+ /** One parsed span of message text. */
39
+ export interface TelegramEntity {
40
+ readonly type: string;
41
+ readonly offset: number;
42
+ readonly length: number;
43
+ /** Present on a `text_mention`, which is how a user without a @handle is named. */
44
+ readonly user?: TelegramUser;
45
+ }
46
+ /** An incoming message. */
47
+ export interface TelegramMessage {
48
+ readonly message_id: number;
49
+ readonly date?: number;
50
+ readonly chat: TelegramChat;
51
+ readonly from?: TelegramUser;
52
+ readonly text?: string;
53
+ readonly caption?: string;
54
+ readonly message_thread_id?: number;
55
+ /**
56
+ * Shared by every message of one album.
57
+ *
58
+ * Telegram has no "several photos in one message": an album arrives as N
59
+ * separate updates tied together only by this id, with the caption on one of
60
+ * them. Treating them as N messages is why sending three screenshots used to
61
+ * start three conversations' worth of turns, two of them contextless.
62
+ */
63
+ readonly media_group_id?: string;
64
+ /**
65
+ * The message this one replies to, with enough of its sender to tell whether
66
+ * it was ours — which is how a group conversation continues without an
67
+ * @mention on every line.
68
+ */
69
+ readonly reply_to_message?: {
70
+ readonly message_id: number;
71
+ readonly from?: TelegramUser;
72
+ };
73
+ /** Text markup Telegram parsed, including the mentions this bot looks for. */
74
+ readonly entities?: readonly TelegramEntity[];
75
+ /** The same, for a photo or document caption. */
76
+ readonly caption_entities?: readonly TelegramEntity[];
77
+ /**
78
+ * Every size Telegram generated, smallest first. The dimensions matter: the
79
+ * largest is routinely bigger than the harness will store.
80
+ */
81
+ readonly photo?: readonly {
82
+ readonly file_id: string;
83
+ readonly file_size?: number;
84
+ readonly width?: number;
85
+ readonly height?: number;
86
+ }[];
87
+ readonly document?: {
88
+ readonly file_id: string;
89
+ readonly file_name?: string;
90
+ readonly mime_type?: string;
91
+ };
92
+ readonly voice?: {
93
+ readonly file_id: string;
94
+ readonly duration?: number;
95
+ };
96
+ readonly audio?: {
97
+ readonly file_id: string;
98
+ readonly mime_type?: string;
99
+ };
100
+ readonly video?: {
101
+ readonly file_id: string;
102
+ readonly mime_type?: string;
103
+ };
104
+ }
105
+ /**
106
+ * A button press. This is the update type the existing channel plugin never
107
+ * subscribes to, and the reason questions cannot be answered from Telegram.
108
+ */
109
+ export interface TelegramCallbackQuery {
110
+ readonly id: string;
111
+ readonly from: TelegramUser;
112
+ readonly data?: string;
113
+ readonly message?: TelegramMessage;
114
+ }
115
+ /** One entry from `getUpdates`. */
116
+ export interface TelegramUpdate {
117
+ readonly update_id: number;
118
+ readonly message?: TelegramMessage;
119
+ readonly edited_message?: TelegramMessage;
120
+ readonly callback_query?: TelegramCallbackQuery;
121
+ }
122
+ /** File metadata from `getFile`. */
123
+ export interface TelegramFile {
124
+ readonly file_id: string;
125
+ readonly file_size?: number;
126
+ readonly file_path?: string;
127
+ }
128
+ /** Chat actions the adapter uses as a progress indicator. */
129
+ export type ChatAction = 'typing' | 'upload_document' | 'upload_photo';
130
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/telegram/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The slice of the Telegram Bot API this plugin actually speaks.
3
+ *
4
+ * Deliberately partial: every field here is one the adapter reads, so an
5
+ * upstream schema change surfaces as a type error at the exact use site rather
6
+ * than as a silently-undefined value deep in the pipeline.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/telegram/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Keeping Telegram's own "typing…" indicator alive.
3
+ *
4
+ * `sendChatAction` lasts five seconds and then lapses, which is fine for the
5
+ * work it was written for and useless for the work this bot does: reading an
6
+ * image on a vision model, waiting behind another message, or a turn that
7
+ * spends a minute in a tool call all outlast it several times over. One call
8
+ * therefore reads as a bot that started and died.
9
+ *
10
+ * So a hold is taken for as long as nothing is visible in the chat, and the
11
+ * action is re-sent inside its own expiry until the hold is released. Holds
12
+ * are counted per conversation: the router's hold over reading an attachment
13
+ * and the bridge's hold over a turn overlap, and the indicator should stop
14
+ * when the last of them lets go, not the first.
15
+ *
16
+ * Everything here is cosmetic, so nothing it does may fail a message.
17
+ */
18
+ import type { ChatTarget } from '../interact/surface.js';
19
+ /** The one Bot API call this needs. */
20
+ export interface ChatActionSender {
21
+ sendChatAction?(chatId: string, action: 'typing', threadId?: number): Promise<void>;
22
+ }
23
+ /** Construction options. */
24
+ export interface TypingIndicatorOptions {
25
+ readonly chat: ChatActionSender;
26
+ readonly refreshMs?: number;
27
+ readonly maxHoldMs?: number;
28
+ }
29
+ export declare class TypingIndicator {
30
+ private readonly options;
31
+ private readonly held;
32
+ constructor(options: TypingIndicatorOptions);
33
+ /**
34
+ * Show that this conversation is being worked on, until released.
35
+ *
36
+ * @param target - the conversation to show it in.
37
+ * @returns a release function, safe to call more than once.
38
+ */
39
+ hold(target: ChatTarget): () => void;
40
+ /** Stop every indicator — the plugin is unloading. */
41
+ dispose(): void;
42
+ /** Send one action, swallowing whatever it does; this is decoration. */
43
+ private send;
44
+ /** Forget a conversation's indicator and stop its timers. */
45
+ private clear;
46
+ }
47
+ //# sourceMappingURL=typing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typing.d.ts","sourceRoot":"","sources":["../../src/telegram/typing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAaxD,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,cAAc,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpF;AAED,4BAA4B;AAC5B,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AASD,qBAAa,eAAe;IAGd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0B;gBAElB,OAAO,EAAE,sBAAsB;IAE5D;;;;;OAKG;IACH,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,IAAI;IA4BpC,sDAAsD;IACtD,OAAO,IAAI,IAAI;IAIf,wEAAwE;IACxE,OAAO,CAAC,IAAI;IAMZ,6DAA6D;IAC7D,OAAO,CAAC,KAAK;CAQd"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Keeping Telegram's own "typing…" indicator alive.
3
+ *
4
+ * `sendChatAction` lasts five seconds and then lapses, which is fine for the
5
+ * work it was written for and useless for the work this bot does: reading an
6
+ * image on a vision model, waiting behind another message, or a turn that
7
+ * spends a minute in a tool call all outlast it several times over. One call
8
+ * therefore reads as a bot that started and died.
9
+ *
10
+ * So a hold is taken for as long as nothing is visible in the chat, and the
11
+ * action is re-sent inside its own expiry until the hold is released. Holds
12
+ * are counted per conversation: the router's hold over reading an attachment
13
+ * and the bridge's hold over a turn overlap, and the indicator should stop
14
+ * when the last of them lets go, not the first.
15
+ *
16
+ * Everything here is cosmetic, so nothing it does may fail a message.
17
+ */
18
+ /** How often to re-send. Comfortably inside Telegram's five-second expiry. */
19
+ const REFRESH_MS = 4_000;
20
+ /**
21
+ * How long one hold may keep the indicator alive.
22
+ *
23
+ * A backstop, not a policy: a release that never runs — a listener dropped, a
24
+ * turn whose end never arrives — would otherwise leave a chat typing forever.
25
+ */
26
+ const MAX_HOLD_MS = 10 * 60 * 1000;
27
+ export class TypingIndicator {
28
+ options;
29
+ held = new Map();
30
+ constructor(options) {
31
+ this.options = options;
32
+ }
33
+ /**
34
+ * Show that this conversation is being worked on, until released.
35
+ *
36
+ * @param target - the conversation to show it in.
37
+ * @returns a release function, safe to call more than once.
38
+ */
39
+ hold(target) {
40
+ const key = keyOf(target);
41
+ const existing = this.held.get(key);
42
+ if (existing) {
43
+ existing.holders += 1;
44
+ }
45
+ else {
46
+ this.send(target);
47
+ this.held.set(key, {
48
+ holders: 1,
49
+ timer: setInterval(() => this.send(target), this.options.refreshMs ?? REFRESH_MS),
50
+ deadline: setTimeout(() => this.clear(key), this.options.maxHoldMs ?? MAX_HOLD_MS),
51
+ });
52
+ }
53
+ let released = false;
54
+ return () => {
55
+ if (released)
56
+ return;
57
+ released = true;
58
+ const entry = this.held.get(key);
59
+ if (!entry)
60
+ return;
61
+ entry.holders -= 1;
62
+ if (entry.holders <= 0)
63
+ this.clear(key);
64
+ };
65
+ }
66
+ /** Stop every indicator — the plugin is unloading. */
67
+ dispose() {
68
+ for (const key of [...this.held.keys()])
69
+ this.clear(key);
70
+ }
71
+ /** Send one action, swallowing whatever it does; this is decoration. */
72
+ send(target) {
73
+ void this.options.chat
74
+ .sendChatAction?.(target.chatId, 'typing', target.threadId)
75
+ .catch(() => undefined);
76
+ }
77
+ /** Forget a conversation's indicator and stop its timers. */
78
+ clear(key) {
79
+ const entry = this.held.get(key);
80
+ if (!entry)
81
+ return;
82
+ this.held.delete(key);
83
+ clearInterval(entry.timer);
84
+ clearTimeout(entry.deadline);
85
+ }
86
+ }
87
+ /** A key distinguishing forum topics, which are separate conversations. */
88
+ function keyOf(target) {
89
+ return target.threadId === undefined ? target.chatId : `${target.chatId}#${target.threadId}`;
90
+ }
91
+ //# sourceMappingURL=typing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typing.js","sourceRoot":"","sources":["../../src/telegram/typing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,8EAA8E;AAC9E,MAAM,UAAU,GAAG,KAAK,CAAA;AAExB;;;;;GAKG;AACH,MAAM,WAAW,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAqBlC,MAAM,OAAO,eAAe;IAGG;IAFZ,IAAI,GAAG,IAAI,GAAG,EAAgB,CAAA;IAE/C,YAA6B,OAA+B;QAA/B,YAAO,GAAP,OAAO,CAAwB;IAAG,CAAC;IAEhE;;;;;OAKG;IACH,IAAI,CAAC,MAAkB;QACrB,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAA;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAEnC,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAA;QACvB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACjB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE;gBACjB,OAAO,EAAE,CAAC;gBACV,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,UAAU,CAAC;gBACjF,QAAQ,EAAE,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,WAAW,CAAC;aACnF,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,QAAQ,GAAG,KAAK,CAAA;QACpB,OAAO,GAAG,EAAE;YACV,IAAI,QAAQ;gBAAE,OAAM;YACpB,QAAQ,GAAG,IAAI,CAAA;YAEf,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAChC,IAAI,CAAC,KAAK;gBAAE,OAAM;YAElB,KAAK,CAAC,OAAO,IAAI,CAAC,CAAA;YAClB,IAAI,KAAK,CAAC,OAAO,IAAI,CAAC;gBAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACzC,CAAC,CAAA;IACH,CAAC;IAED,sDAAsD;IACtD,OAAO;QACL,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC1D,CAAC;IAED,wEAAwE;IAChE,IAAI,CAAC,MAAkB;QAC7B,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI;aACnB,cAAc,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;aAC1D,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IAC3B,CAAC;IAED,6DAA6D;IACrD,KAAK,CAAC,GAAW;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,CAAC,KAAK;YAAE,OAAM;QAElB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACrB,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC1B,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAC9B,CAAC;CACF;AAED,2EAA2E;AAC3E,SAAS,KAAK,CAAC,MAAkB;IAC/B,OAAO,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAA;AAC9F,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * What is installed here, and what is published.
3
+ *
4
+ * The question behind "should there be an /update" is really "am I behind?",
5
+ * and that one can be answered without any of the risk. Updating the harness
6
+ * takes effect only after a restart, and restarting it from inside a plugin
7
+ * running in it kills the process answering you — with nothing to bring it
8
+ * back on a machine with no supervisor. So this reports, and the person
9
+ * decides when and where to act on it.
10
+ *
11
+ * Both halves degrade quietly. A version that cannot be read reports as
12
+ * unknown, and a registry that cannot be reached simply says nothing about
13
+ * what is published; neither is worth failing a command over.
14
+ */
15
+ /** One package's installed and published versions. */
16
+ export interface VersionReport {
17
+ readonly name: string;
18
+ readonly installed: string;
19
+ readonly latest?: string;
20
+ /** Whether what is published is newer than what is installed. */
21
+ readonly behind: boolean;
22
+ }
23
+ /** Fetching, injected so tests need no network. */
24
+ export type FetchLike = (url: string, init: {
25
+ signal: AbortSignal;
26
+ }) => Promise<{
27
+ ok: boolean;
28
+ json(): Promise<unknown>;
29
+ }>;
30
+ /** Construction options. */
31
+ export interface VersionCheckOptions {
32
+ readonly fetchImpl?: FetchLike;
33
+ readonly cacheMs?: number;
34
+ readonly timeoutMs?: number;
35
+ readonly now?: () => number;
36
+ }
37
+ export declare class VersionCheck {
38
+ private readonly options;
39
+ private readonly cache;
40
+ constructor(options?: VersionCheckOptions);
41
+ /**
42
+ * Compare one installed version against what npm publishes.
43
+ *
44
+ * @param name - the package name.
45
+ * @param installed - the version running here.
46
+ */
47
+ check(name: string, installed: string): Promise<VersionReport>;
48
+ /** The published version, from cache when it is fresh enough. */
49
+ private latest;
50
+ /** Ask the registry, and say nothing if it cannot be reached. */
51
+ private ask;
52
+ }
53
+ /**
54
+ * Compare two semantic versions.
55
+ *
56
+ * Prereleases are the reason this is written out rather than compared as
57
+ * strings: the harness ships as `0.1.0-rc.8`, and `"0.1.0-rc.8" < "0.1.0-rc.10"`
58
+ * is false as text and true as versions.
59
+ *
60
+ * @returns negative when `a` is older, positive when newer, zero when equal.
61
+ */
62
+ export declare function compareVersions(a: string, b: string): number;
63
+ //# sourceMappingURL=versions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versions.d.ts","sourceRoot":"","sources":["../src/versions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,sDAAsD;AACtD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED,mDAAmD;AACnD,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,KAAK,OAAO,CAAC;IAC9E,EAAE,EAAE,OAAO,CAAA;IACX,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CACzB,CAAC,CAAA;AAEF,4BAA4B;AAC5B,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAA;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAC5B;AAED,qBAAa,YAAY;IAGX,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgE;gBAEzD,OAAO,GAAE,mBAAwB;IAE9D;;;;;OAKG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAUpE,iEAAiE;YACnD,MAAM;IAUpB,iEAAiE;YACnD,GAAG;CAwBlB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAkB5D"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * What is installed here, and what is published.
3
+ *
4
+ * The question behind "should there be an /update" is really "am I behind?",
5
+ * and that one can be answered without any of the risk. Updating the harness
6
+ * takes effect only after a restart, and restarting it from inside a plugin
7
+ * running in it kills the process answering you — with nothing to bring it
8
+ * back on a machine with no supervisor. So this reports, and the person
9
+ * decides when and where to act on it.
10
+ *
11
+ * Both halves degrade quietly. A version that cannot be read reports as
12
+ * unknown, and a registry that cannot be reached simply says nothing about
13
+ * what is published; neither is worth failing a command over.
14
+ */
15
+ /** How long a registry answer is good for. Long enough that /diag is free. */
16
+ const CACHE_MS = 60 * 60 * 1000;
17
+ /** How long to wait on the registry before giving up on it. */
18
+ const TIMEOUT_MS = 4000;
19
+ export class VersionCheck {
20
+ options;
21
+ cache = new Map();
22
+ constructor(options = {}) {
23
+ this.options = options;
24
+ }
25
+ /**
26
+ * Compare one installed version against what npm publishes.
27
+ *
28
+ * @param name - the package name.
29
+ * @param installed - the version running here.
30
+ */
31
+ async check(name, installed) {
32
+ const latest = await this.latest(name);
33
+ return {
34
+ name,
35
+ installed,
36
+ ...(latest === undefined ? {} : { latest }),
37
+ behind: latest !== undefined && compareVersions(installed, latest) < 0,
38
+ };
39
+ }
40
+ /** The published version, from cache when it is fresh enough. */
41
+ async latest(name) {
42
+ const now = this.options.now?.() ?? Date.now();
43
+ const cached = this.cache.get(name);
44
+ if (cached && now - cached.at < (this.options.cacheMs ?? CACHE_MS))
45
+ return cached.latest;
46
+ const latest = await this.ask(name);
47
+ this.cache.set(name, { at: now, latest });
48
+ return latest;
49
+ }
50
+ /** Ask the registry, and say nothing if it cannot be reached. */
51
+ async ask(name) {
52
+ const fetchImpl = this.options.fetchImpl ?? globalThis.fetch;
53
+ if (fetchImpl === undefined)
54
+ return undefined;
55
+ const controller = new AbortController();
56
+ const timer = setTimeout(() => controller.abort(), this.options.timeoutMs ?? TIMEOUT_MS);
57
+ try {
58
+ // The `latest` dist-tag endpoint rather than the full document: a
59
+ // package's full metadata can be megabytes, and only one field is wanted.
60
+ const response = await fetchImpl(`https://registry.npmjs.org/${name.replace('/', '%2F')}/latest`, { signal: controller.signal });
61
+ if (!response.ok)
62
+ return undefined;
63
+ const body = (await response.json());
64
+ return typeof body.version === 'string' ? body.version : undefined;
65
+ }
66
+ catch {
67
+ return undefined;
68
+ }
69
+ finally {
70
+ clearTimeout(timer);
71
+ }
72
+ }
73
+ }
74
+ /**
75
+ * Compare two semantic versions.
76
+ *
77
+ * Prereleases are the reason this is written out rather than compared as
78
+ * strings: the harness ships as `0.1.0-rc.8`, and `"0.1.0-rc.8" < "0.1.0-rc.10"`
79
+ * is false as text and true as versions.
80
+ *
81
+ * @returns negative when `a` is older, positive when newer, zero when equal.
82
+ */
83
+ export function compareVersions(a, b) {
84
+ const [coreA = '', preA = ''] = splitPrerelease(a);
85
+ const [coreB = '', preB = ''] = splitPrerelease(b);
86
+ const numbersA = coreA.split('.').map((part) => Number.parseInt(part, 10) || 0);
87
+ const numbersB = coreB.split('.').map((part) => Number.parseInt(part, 10) || 0);
88
+ for (let index = 0; index < Math.max(numbersA.length, numbersB.length); index += 1) {
89
+ const difference = (numbersA[index] ?? 0) - (numbersB[index] ?? 0);
90
+ if (difference !== 0)
91
+ return difference;
92
+ }
93
+ // A release outranks any prerelease of the same core: 1.0.0 beats 1.0.0-rc.9.
94
+ if (preA === '' && preB === '')
95
+ return 0;
96
+ if (preA === '')
97
+ return 1;
98
+ if (preB === '')
99
+ return -1;
100
+ return comparePrerelease(preA, preB);
101
+ }
102
+ /** Split `1.2.3-rc.4` into its core and its prerelease. */
103
+ function splitPrerelease(version) {
104
+ const trimmed = version.trim().replace(/^v/, '').split('+')[0] ?? '';
105
+ const dash = trimmed.indexOf('-');
106
+ return dash === -1 ? [trimmed, ''] : [trimmed.slice(0, dash), trimmed.slice(dash + 1)];
107
+ }
108
+ /** Compare prerelease tags dot-part by dot-part, numbers numerically. */
109
+ function comparePrerelease(a, b) {
110
+ const partsA = a.split('.');
111
+ const partsB = b.split('.');
112
+ for (let index = 0; index < Math.max(partsA.length, partsB.length); index += 1) {
113
+ const one = partsA[index];
114
+ const two = partsB[index];
115
+ // A shorter prerelease sorts first: rc.1 is older than rc.1.1.
116
+ if (one === undefined)
117
+ return -1;
118
+ if (two === undefined)
119
+ return 1;
120
+ if (one === two)
121
+ continue;
122
+ const numericOne = /^\d+$/.test(one);
123
+ const numericTwo = /^\d+$/.test(two);
124
+ // Numeric parts compare as numbers, and always sort below alphanumeric.
125
+ if (numericOne && numericTwo)
126
+ return Number(one) - Number(two);
127
+ if (numericOne)
128
+ return -1;
129
+ if (numericTwo)
130
+ return 1;
131
+ return one < two ? -1 : 1;
132
+ }
133
+ return 0;
134
+ }
135
+ //# sourceMappingURL=versions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versions.js","sourceRoot":"","sources":["../src/versions.ts"],"names":[],"mappings":"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"}