@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,255 @@
1
+ /**
2
+ * Answering the agent's questions from Telegram.
3
+ *
4
+ * This is the half of the plugin that has no equivalent in the existing
5
+ * channel bridge. When the agent calls `ask_user_question`, the harness blocks
6
+ * the tool call on `ctx.userQuestions.ask()` and waits for the single
7
+ * registered UI provider to return an answer. Until now that provider was
8
+ * always the browser, so a conversation held entirely in Telegram would stall
9
+ * on the first question with no way to answer it.
10
+ *
11
+ * This provider renders each question as a message with an inline keyboard,
12
+ * parks the promise, and resolves it when a button is pressed — minutes later,
13
+ * in a different HTTP request, possibly on a different device.
14
+ *
15
+ * Two details are load-bearing:
16
+ *
17
+ * - **Delegation.** The harness allows exactly one provider. When the browser
18
+ * already registered one, this provider takes over and forwards every
19
+ * question that does not belong to a Telegram-bound session back to it, so
20
+ * installing this plugin never takes the web UI's questions away.
21
+ * - **Multi-select re-parks.** A toggle is not an answer, so each press settles
22
+ * its waiter and opens a fresh one for the redrawn keyboard. The token
23
+ * changes every round, which also makes a stale button inert.
24
+ */
25
+ import { escapeHtml } from '../render/escape.js';
26
+ import { clamp, escapeWithin } from '../render/clamp.js';
27
+ /** Prefix marking callback data as belonging to a question. */
28
+ const KIND = 'q';
29
+ /** Index reserved for the "answer in your own words" button. */
30
+ const OTHER_INDEX = -1;
31
+ /** Index reserved for the multi-select "Done" button. */
32
+ const DONE_INDEX = -2;
33
+ /** Telegram truncates long button labels awkwardly; do it deliberately instead. */
34
+ const MAX_BUTTON_LABEL = 48;
35
+ /**
36
+ * Budgets for the model-authored parts of a prompt. Together with the option
37
+ * cap below they keep the assembled message inside Telegram's 4096-character
38
+ * limit, so a verbose question can never become a question nobody is shown.
39
+ */
40
+ const HEADER_BUDGET = 120;
41
+ const QUESTION_BUDGET = 1200;
42
+ const DESCRIPTION_BUDGET = 220;
43
+ /** Descriptions rendered in the body; the rest are still selectable buttons. */
44
+ const MAX_DESCRIBED_OPTIONS = 8;
45
+ /** Thrown when the agent abandons a question before the user answers it. */
46
+ export class QuestionCancelledError extends Error {
47
+ constructor() {
48
+ super('ask_user_question was cancelled before the user answered');
49
+ this.name = 'QuestionCancelledError';
50
+ }
51
+ }
52
+ /** Thrown when a question arrives for a session with nowhere to ask it. */
53
+ export class NoChatError extends Error {
54
+ constructor(sessionId) {
55
+ super(`no telegram chat is bound to session '${sessionId}'`);
56
+ this.name = 'NoChatError';
57
+ }
58
+ }
59
+ export class TelegramQuestionProvider {
60
+ options;
61
+ /**
62
+ * Where non-Telegram questions go. Settable after construction because the
63
+ * incumbent provider is only knowable at the moment this one displaces it.
64
+ */
65
+ fallback;
66
+ constructor(options) {
67
+ this.options = options;
68
+ this.fallback = options.fallback;
69
+ }
70
+ /**
71
+ * Point delegation at the provider this one displaced.
72
+ *
73
+ * @param provider - the incumbent, which keeps answering its own sessions.
74
+ */
75
+ setFallback(provider) {
76
+ this.fallback = provider;
77
+ }
78
+ /**
79
+ * Ask the human, one question at a time.
80
+ *
81
+ * @param request - the questions, the owning agent, and the agent's signal.
82
+ * @returns the answers, in request order.
83
+ * @throws {NoChatError} when the session has no Telegram chat and no
84
+ * provider to delegate to.
85
+ * @throws {QuestionCancelledError} when the agent gives up first.
86
+ */
87
+ async ask(request) {
88
+ const sessionId = request.agent?.session.id ?? request.agent?.id;
89
+ const target = sessionId === undefined ? undefined : this.options.targetOf(sessionId);
90
+ if (!target) {
91
+ if (this.fallback)
92
+ return this.fallback.ask(request);
93
+ throw new NoChatError(sessionId ?? '<unknown>');
94
+ }
95
+ const answers = [];
96
+ for (const question of request.questions) {
97
+ answers.push(await this.askOne(question, target, request.signal));
98
+ }
99
+ return { answers };
100
+ }
101
+ /**
102
+ * Route one button press.
103
+ *
104
+ * @param data - raw `callback_data` from the update.
105
+ * @returns whether the press belonged to an open question.
106
+ */
107
+ handleCallback(data) {
108
+ const press = decodeCallback(data);
109
+ if (!press)
110
+ return false;
111
+ return this.options.pending.settle(press.token, press);
112
+ }
113
+ /** Ask one question and hold the chat open until it is answered. */
114
+ async askOne(question, target, signal) {
115
+ if (question.detail !== undefined) {
116
+ // Agent-authored markdown — a plan, a diff — so Telegram renders it.
117
+ await this.options.surface.sendMarkdown(target, question.detail);
118
+ }
119
+ const options = question.options ?? [];
120
+ if (options.length === 0)
121
+ return this.askFreeText(question, target, signal);
122
+ let selected = [];
123
+ let messageId;
124
+ for (;;) {
125
+ const waiter = this.options.pending.open({
126
+ ...(signal ? { signal } : {}),
127
+ onCancel: () => void this.retire(target, messageId, question, 'Cancelled.'),
128
+ });
129
+ const html = renderPrompt(question, selected);
130
+ const keyboard = buildKeyboard(question, selected, waiter.token);
131
+ if (messageId === undefined)
132
+ messageId = await this.options.surface.send(target, html, keyboard);
133
+ else
134
+ await this.options.surface.edit(target, messageId, html, keyboard);
135
+ const press = (await waiter.promise);
136
+ if (!press)
137
+ throw new QuestionCancelledError();
138
+ if (press.index === OTHER_INDEX) {
139
+ const custom = await this.options.readText(target, signal);
140
+ if (custom === undefined)
141
+ throw new QuestionCancelledError();
142
+ await this.retire(target, messageId, question, `Answered: ${clamp(custom, 200)}`);
143
+ return { id: question.id, selected: [], custom };
144
+ }
145
+ if (press.index === DONE_INDEX) {
146
+ const labels = selected.map((index) => options[index]?.label ?? '');
147
+ await this.retire(target, messageId, question, summary(labels));
148
+ return { id: question.id, selected: labels };
149
+ }
150
+ if (!question.multiSelect) {
151
+ const label = options[press.index]?.label;
152
+ if (label === undefined)
153
+ continue;
154
+ await this.retire(target, messageId, question, summary([label]));
155
+ return { id: question.id, selected: [label] };
156
+ }
157
+ selected = toggle(selected, press.index);
158
+ }
159
+ }
160
+ /** A question with no options is an open one: read the user's next message. */
161
+ async askFreeText(question, target, signal) {
162
+ await this.options.surface.send(target, renderPrompt(question, []));
163
+ const custom = await this.options.readText(target, signal);
164
+ if (custom === undefined)
165
+ throw new QuestionCancelledError();
166
+ return { id: question.id, selected: [], custom };
167
+ }
168
+ /** Replace a prompt with its outcome and take the buttons away. */
169
+ async retire(target, messageId, question, outcome) {
170
+ if (messageId === undefined)
171
+ return;
172
+ const html = `${renderPrompt(question, [])}\n\n<b>${escapeWithin(outcome, 400)}</b>`;
173
+ await this.options.surface.edit(target, messageId, html, []).catch(() => undefined);
174
+ }
175
+ }
176
+ /**
177
+ * Decode `callback_data` produced by this module.
178
+ *
179
+ * @param data - raw callback data, from an untrusted update.
180
+ * @returns the press, or undefined when the data belongs elsewhere or is malformed.
181
+ */
182
+ export function decodeCallback(data) {
183
+ if (data === undefined)
184
+ return undefined;
185
+ const parts = data.split(':');
186
+ if (parts.length !== 3 || parts[0] !== KIND)
187
+ return undefined;
188
+ const token = parts[1];
189
+ const index = Number(parts[2]);
190
+ if (token === '' || !Number.isInteger(index))
191
+ return undefined;
192
+ return { token, index };
193
+ }
194
+ /** Add or remove an index, preserving the order options were chosen in. */
195
+ function toggle(selected, index) {
196
+ return selected.includes(index)
197
+ ? selected.filter((value) => value !== index)
198
+ : [...selected, index];
199
+ }
200
+ /** The question itself: header, prompt, and any option descriptions. */
201
+ function renderPrompt(question, selected) {
202
+ const lines = [];
203
+ if (question.header)
204
+ lines.push(`<b>${escapeWithin(question.header, HEADER_BUDGET)}</b>`);
205
+ lines.push(escapeWithin(question.question, QUESTION_BUDGET));
206
+ const described = (question.options ?? [])
207
+ .filter((option) => option.description)
208
+ .slice(0, MAX_DESCRIBED_OPTIONS);
209
+ if (described.length > 0) {
210
+ lines.push('');
211
+ for (const option of described) {
212
+ lines.push(`• <b>${escapeWithin(option.label, MAX_BUTTON_LABEL * 2)}</b> — ` +
213
+ `${escapeWithin(option.description, DESCRIPTION_BUDGET)}`);
214
+ }
215
+ }
216
+ if (question.multiSelect) {
217
+ lines.push('');
218
+ lines.push(`<i>Pick any number, then press Done (${selected.length} selected).</i>`);
219
+ }
220
+ return lines.join('\n');
221
+ }
222
+ /** Buttons for one question: the options, plus Done and Other where they apply. */
223
+ function buildKeyboard(question, selected, token) {
224
+ const approve = question.intent?.approve;
225
+ const rows = (question.options ?? []).map((option, index) => [
226
+ {
227
+ text: label(option.label, {
228
+ checked: question.multiSelect === true && selected.includes(index),
229
+ approving: approve !== undefined && option.label === approve,
230
+ }),
231
+ callbackData: `${KIND}:${token}:${index}`,
232
+ },
233
+ ]);
234
+ if (question.multiSelect) {
235
+ rows.push([{ text: '✅ Done', callbackData: `${KIND}:${token}:${DONE_INDEX}` }]);
236
+ }
237
+ else {
238
+ rows.push([{ text: '✏️ Other…', callbackData: `${KIND}:${token}:${OTHER_INDEX}` }]);
239
+ }
240
+ return rows;
241
+ }
242
+ /** One button label: trimmed to length, marked as chosen or as the approval. */
243
+ function label(text, marks) {
244
+ const trimmed = text.length > MAX_BUTTON_LABEL ? `${text.slice(0, MAX_BUTTON_LABEL - 1)}…` : text;
245
+ if (marks.checked)
246
+ return `✅ ${trimmed}`;
247
+ if (marks.approving)
248
+ return `👍 ${trimmed}`;
249
+ return trimmed;
250
+ }
251
+ /** Human summary of what was chosen, for the retired prompt. */
252
+ function summary(labels) {
253
+ return labels.length === 0 ? 'Answered: nothing selected' : `Answered: ${labels.join(', ')}`;
254
+ }
255
+ //# sourceMappingURL=questions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questions.js","sourceRoot":"","sources":["../../src/interact/questions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAaxD,+DAA+D;AAC/D,MAAM,IAAI,GAAG,GAAG,CAAA;AAEhB,gEAAgE;AAChE,MAAM,WAAW,GAAG,CAAC,CAAC,CAAA;AAEtB,yDAAyD;AACzD,MAAM,UAAU,GAAG,CAAC,CAAC,CAAA;AAErB,mFAAmF;AACnF,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAE3B;;;;GAIG;AACH,MAAM,aAAa,GAAG,GAAG,CAAA;AACzB,MAAM,eAAe,GAAG,IAAI,CAAA;AAC5B,MAAM,kBAAkB,GAAG,GAAG,CAAA;AAC9B,gFAAgF;AAChF,MAAM,qBAAqB,GAAG,CAAC,CAAA;AAE/B,4EAA4E;AAC5E,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/C;QACE,KAAK,CAAC,0DAA0D,CAAC,CAAA;QACjE,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAA;IACtC,CAAC;CACF;AAED,2EAA2E;AAC3E,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,SAAiB;QAC3B,KAAK,CAAC,yCAAyC,SAAS,GAAG,CAAC,CAAA;QAC5D,IAAI,CAAC,IAAI,GAAG,aAAa,CAAA;IAC3B,CAAC;CACF;AAsBD,MAAM,OAAO,wBAAwB;IAON;IAN7B;;;OAGG;IACK,QAAQ,CAAkC;IAElD,YAA6B,OAAgC;QAAhC,YAAO,GAAP,OAAO,CAAyB;QAC3D,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;IAClC,CAAC;IAED;;;;OAIG;IACH,WAAW,CAAC,QAA0C;QACpD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,GAAG,CAAC,OAA4B;QACpC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,KAAK,EAAE,EAAE,CAAA;QAChE,MAAM,MAAM,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;QAErF,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,IAAI,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YACpD,MAAM,IAAI,WAAW,CAAC,SAAS,IAAI,WAAW,CAAC,CAAA;QACjD,CAAC;QAED,MAAM,OAAO,GAAgC,EAAE,CAAA;QAC/C,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;QACnE,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAA;IACpB,CAAC;IAED;;;;;OAKG;IACH,cAAc,CAAC,IAAwB;QACrC,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAA;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IACxD,CAAC;IAED,oEAAoE;IAC5D,KAAK,CAAC,MAAM,CAClB,QAA6B,EAC7B,MAAkB,EAClB,MAA+B;QAE/B,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAClC,qEAAqE;YACrE,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;QAClE,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAA;QACtC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;QAE3E,IAAI,QAAQ,GAAa,EAAE,CAAA;QAC3B,IAAI,SAA6B,CAAA;QAEjC,SAAS,CAAC;YACR,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;gBACvC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7B,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC;aAC5E,CAAC,CAAA;YAEF,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;YAC7C,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;YAEhE,IAAI,SAAS,KAAK,SAAS;gBAAE,SAAS,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAA;;gBAC3F,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAA;YAEvE,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAsB,CAAA;YACzD,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,sBAAsB,EAAE,CAAA;YAE9C,IAAI,KAAK,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;gBAChC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;gBAC1D,IAAI,MAAM,KAAK,SAAS;oBAAE,MAAM,IAAI,sBAAsB,EAAE,CAAA;gBAC5D,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;gBACjF,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAA;YAClD,CAAC;YAED,IAAI,KAAK,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC,CAAA;gBACnE,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;gBAC/D,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;YAC9C,CAAC;YAED,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;gBAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;gBACzC,IAAI,KAAK,KAAK,SAAS;oBAAE,SAAQ;gBACjC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;gBAChE,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,CAAA;YAC/C,CAAC;YAED,QAAQ,GAAG,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;QAC1C,CAAC;IACH,CAAC;IAED,+EAA+E;IACvE,KAAK,CAAC,WAAW,CACvB,QAA6B,EAC7B,MAAkB,EAClB,MAA+B;QAE/B,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAA;QACnE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC1D,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,IAAI,sBAAsB,EAAE,CAAA;QAC5D,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAA;IAClD,CAAC;IAED,mEAAmE;IAC3D,KAAK,CAAC,MAAM,CAClB,MAAkB,EAClB,SAA6B,EAC7B,QAA6B,EAC7B,OAAe;QAEf,IAAI,SAAS,KAAK,SAAS;YAAE,OAAM;QACnC,MAAM,IAAI,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC,UAAU,YAAY,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAA;QACpF,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IACrF,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAwB;IACrD,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAExC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IAE7D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAW,CAAA;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9B,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAE9D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;AACzB,CAAC;AAED,2EAA2E;AAC3E,SAAS,MAAM,CAAC,QAA2B,EAAE,KAAa;IACxD,OAAO,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC7B,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC;QAC7C,CAAC,CAAC,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,CAAA;AAC1B,CAAC;AAED,wEAAwE;AACxE,SAAS,YAAY,CAAC,QAA6B,EAAE,QAA2B;IAC9E,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,IAAI,QAAQ,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,YAAY,CAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACzF,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAA;IAE5D,MAAM,SAAS,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;SACvC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC;SACtC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAA;IAElC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACd,KAAK,MAAM,MAAM,IAAI,SAAS,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CACR,QAAQ,YAAY,CAAC,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,CAAC,CAAC,SAAS;gBAC/D,GAAG,YAAY,CAAC,MAAM,CAAC,WAAqB,EAAE,kBAAkB,CAAC,EAAE,CACtE,CAAA;QACH,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACd,KAAK,CAAC,IAAI,CAAC,wCAAwC,QAAQ,CAAC,MAAM,iBAAiB,CAAC,CAAA;IACtF,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC;AAED,mFAAmF;AACnF,SAAS,aAAa,CACpB,QAA6B,EAC7B,QAA2B,EAC3B,KAAa;IAEb,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxC,MAAM,IAAI,GAAqB,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7E;YACE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE;gBACxB,OAAO,EAAE,QAAQ,CAAC,WAAW,KAAK,IAAI,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAClE,SAAS,EAAE,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,KAAK,OAAO;aAC7D,CAAC;YACF,YAAY,EAAE,GAAG,IAAI,IAAI,KAAK,IAAI,KAAK,EAAE;SAC1C;KACF,CAAC,CAAA;IAEF,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI,IAAI,KAAK,IAAI,UAAU,EAAE,EAAE,CAAC,CAAC,CAAA;IACjF,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,IAAI,IAAI,KAAK,IAAI,WAAW,EAAE,EAAE,CAAC,CAAC,CAAA;IACrF,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,gFAAgF;AAChF,SAAS,KAAK,CAAC,IAAY,EAAE,KAA+C;IAC1E,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;IACjG,IAAI,KAAK,CAAC,OAAO;QAAE,OAAO,KAAK,OAAO,EAAE,CAAA;IACxC,IAAI,KAAK,CAAC,SAAS;QAAE,OAAO,MAAM,OAAO,EAAE,CAAA;IAC3C,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,gEAAgE;AAChE,SAAS,OAAO,CAAC,MAAyB;IACxC,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,aAAa,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;AAC9F,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The narrow chat surface the interactive prompts are written against.
3
+ *
4
+ * Questions and approvals only ever need to put a message in a chat and later
5
+ * change it. Depending on that instead of the full Bot API client keeps those
6
+ * modules testable with a few lines of stub, and keeps Telegram's request
7
+ * shapes in one place.
8
+ */
9
+ import type { TelegramApi } from '../telegram/api.js';
10
+ import type { InlineKeyboard } from '../telegram/types.js';
11
+ /** Where a prompt is delivered. */
12
+ export interface ChatTarget {
13
+ readonly chatId: string;
14
+ readonly threadId?: number;
15
+ }
16
+ /** Post and revise messages in a chat. */
17
+ export interface ChatSurface {
18
+ /**
19
+ * Post a message built by this plugin. HTML, because these are short,
20
+ * assembled from escaped text, and carry the inline keyboards.
21
+ */
22
+ send(target: ChatTarget, html: string, keyboard?: InlineKeyboard): Promise<number>;
23
+ /** Revise a message in place. Passing an empty keyboard retires the buttons. */
24
+ edit(target: ChatTarget, messageId: number, html: string, keyboard?: InlineKeyboard): Promise<void>;
25
+ /**
26
+ * Post agent-authored markdown, which Telegram renders itself — the path a
27
+ * plan or a diff takes, so its tables and code blocks survive.
28
+ */
29
+ sendMarkdown(target: ChatTarget, markdown: string): Promise<number>;
30
+ }
31
+ /** Bind a {@link ChatSurface} to the live Bot API client. */
32
+ export declare function telegramSurface(api: TelegramApi): ChatSurface;
33
+ //# sourceMappingURL=surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/interact/surface.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1D,mCAAmC;AACnC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,0CAA0C;AAC1C,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAClF,gFAAgF;IAChF,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnG;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACpE;AAED,6DAA6D;AAC7D,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW,GAAG,WAAW,CA8B7D"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The narrow chat surface the interactive prompts are written against.
3
+ *
4
+ * Questions and approvals only ever need to put a message in a chat and later
5
+ * change it. Depending on that instead of the full Bot API client keeps those
6
+ * modules testable with a few lines of stub, and keeps Telegram's request
7
+ * shapes in one place.
8
+ */
9
+ /** Bind a {@link ChatSurface} to the live Bot API client. */
10
+ export function telegramSurface(api) {
11
+ return {
12
+ async send(target, html, keyboard) {
13
+ const sent = await api.sendMessage({
14
+ chatId: target.chatId,
15
+ html,
16
+ ...(keyboard ? { keyboard } : {}),
17
+ ...(target.threadId !== undefined ? { threadId: target.threadId } : {}),
18
+ });
19
+ return sent.messageId;
20
+ },
21
+ async edit(target, messageId, html, keyboard) {
22
+ await api.editMessageText({
23
+ chatId: target.chatId,
24
+ messageId,
25
+ html,
26
+ ...(keyboard ? { keyboard } : {}),
27
+ });
28
+ },
29
+ async sendMarkdown(target, markdown) {
30
+ const sent = await api.sendRichMessage({
31
+ chatId: target.chatId,
32
+ markdown,
33
+ ...(target.threadId !== undefined ? { threadId: target.threadId } : {}),
34
+ });
35
+ return sent.messageId;
36
+ },
37
+ };
38
+ }
39
+ //# sourceMappingURL=surface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surface.js","sourceRoot":"","sources":["../../src/interact/surface.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA2BH,6DAA6D;AAC7D,MAAM,UAAU,eAAe,CAAC,GAAgB;IAC9C,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ;YAC/B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC;gBACjC,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,IAAI;gBACJ,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjC,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE,CAAC,CAAA;YACF,OAAO,IAAI,CAAC,SAAS,CAAA;QACvB,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ;YAC1C,MAAM,GAAG,CAAC,eAAe,CAAC;gBACxB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,SAAS;gBACT,IAAI;gBACJ,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAClC,CAAC,CAAA;QACJ,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ;YACjC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,eAAe,CAAC;gBACrC,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ;gBACR,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE,CAAC,CAAA;YACF,OAAO,IAAI,CAAC,SAAS,CAAA;QACvB,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Borrowing the next chat message as an answer.
3
+ *
4
+ * When a question offers no options — or the user picks "Other…" — the answer
5
+ * is whatever they type next. That message would otherwise be routed to the
6
+ * agent as a new prompt, so the capture has to be consulted before ordinary
7
+ * routing, and it has to be per-conversation: a question pending in one chat
8
+ * must not swallow a message typed in another.
9
+ */
10
+ import type { ChatTarget } from './surface.js';
11
+ export declare class TextCapture {
12
+ private readonly waiting;
13
+ /**
14
+ * Wait for the next plain-text message in a conversation.
15
+ *
16
+ * A second wait on the same conversation supersedes the first, which is
17
+ * resolved as cancelled — the agent moved on, and only one prompt can own
18
+ * the user's next message.
19
+ *
20
+ * @param target - the conversation to listen in.
21
+ * @param signal - cancels the wait when the agent gives up.
22
+ * @returns the typed text, or undefined when cancelled.
23
+ */
24
+ next(target: ChatTarget, signal?: AbortSignal): Promise<string | undefined>;
25
+ /** Whether a conversation is currently waiting for typed input. */
26
+ isWaiting(target: ChatTarget): boolean;
27
+ /**
28
+ * Hand a message to a waiting prompt.
29
+ *
30
+ * @returns whether a prompt consumed it; false means route it normally.
31
+ */
32
+ deliver(target: ChatTarget, text: string): boolean;
33
+ /** Cancel every wait — the plugin is unloading. */
34
+ dispose(): void;
35
+ /** Cancel one conversation's wait. */
36
+ private cancel;
37
+ }
38
+ //# sourceMappingURL=text-capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-capture.d.ts","sourceRoot":"","sources":["../../src/interact/text-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAQ9C,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IAErD;;;;;;;;;;OAUG;IACH,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAgB3E,mEAAmE;IACnE,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO;IAItC;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAWlD,mDAAmD;IACnD,OAAO,IAAI,IAAI;IAIf,sCAAsC;IACtC,OAAO,CAAC,MAAM;CAQf"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Borrowing the next chat message as an answer.
3
+ *
4
+ * When a question offers no options — or the user picks "Other…" — the answer
5
+ * is whatever they type next. That message would otherwise be routed to the
6
+ * agent as a new prompt, so the capture has to be consulted before ordinary
7
+ * routing, and it has to be per-conversation: a question pending in one chat
8
+ * must not swallow a message typed in another.
9
+ */
10
+ export class TextCapture {
11
+ waiting = new Map();
12
+ /**
13
+ * Wait for the next plain-text message in a conversation.
14
+ *
15
+ * A second wait on the same conversation supersedes the first, which is
16
+ * resolved as cancelled — the agent moved on, and only one prompt can own
17
+ * the user's next message.
18
+ *
19
+ * @param target - the conversation to listen in.
20
+ * @param signal - cancels the wait when the agent gives up.
21
+ * @returns the typed text, or undefined when cancelled.
22
+ */
23
+ next(target, signal) {
24
+ const key = keyOf(target);
25
+ this.cancel(key);
26
+ return new Promise((resolve) => {
27
+ if (signal?.aborted)
28
+ return resolve(undefined);
29
+ const onAbort = () => this.cancel(key);
30
+ this.waiting.set(key, {
31
+ settle: resolve,
32
+ detach: () => signal?.removeEventListener('abort', onAbort),
33
+ });
34
+ signal?.addEventListener('abort', onAbort, { once: true });
35
+ });
36
+ }
37
+ /** Whether a conversation is currently waiting for typed input. */
38
+ isWaiting(target) {
39
+ return this.waiting.has(keyOf(target));
40
+ }
41
+ /**
42
+ * Hand a message to a waiting prompt.
43
+ *
44
+ * @returns whether a prompt consumed it; false means route it normally.
45
+ */
46
+ deliver(target, text) {
47
+ const key = keyOf(target);
48
+ const capture = this.waiting.get(key);
49
+ if (!capture)
50
+ return false;
51
+ this.waiting.delete(key);
52
+ capture.detach();
53
+ capture.settle(text);
54
+ return true;
55
+ }
56
+ /** Cancel every wait — the plugin is unloading. */
57
+ dispose() {
58
+ for (const key of [...this.waiting.keys()])
59
+ this.cancel(key);
60
+ }
61
+ /** Cancel one conversation's wait. */
62
+ cancel(key) {
63
+ const capture = this.waiting.get(key);
64
+ if (!capture)
65
+ return;
66
+ this.waiting.delete(key);
67
+ capture.detach();
68
+ capture.settle(undefined);
69
+ }
70
+ }
71
+ /** Conversation key; a forum topic is its own conversation. */
72
+ function keyOf(target) {
73
+ return target.threadId === undefined ? target.chatId : `${target.chatId}#${target.threadId}`;
74
+ }
75
+ //# sourceMappingURL=text-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-capture.js","sourceRoot":"","sources":["../../src/interact/text-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,MAAM,OAAO,WAAW;IACL,OAAO,GAAG,IAAI,GAAG,EAAmB,CAAA;IAErD;;;;;;;;;;OAUG;IACH,IAAI,CAAC,MAAkB,EAAE,MAAoB;QAC3C,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAA;QACzB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAEhB,OAAO,IAAI,OAAO,CAAqB,CAAC,OAAO,EAAE,EAAE;YACjD,IAAI,MAAM,EAAE,OAAO;gBAAE,OAAO,OAAO,CAAC,SAAS,CAAC,CAAA;YAE9C,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACtC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE;gBACpB,MAAM,EAAE,OAAO;gBACf,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC;aAC5D,CAAC,CAAA;YACF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5D,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,mEAAmE;IACnE,SAAS,CAAC,MAAkB;QAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;IACxC,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,MAAkB,EAAE,IAAY;QACtC,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAA;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACrC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAA;QAE1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACxB,OAAO,CAAC,MAAM,EAAE,CAAA;QAChB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACpB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,mDAAmD;IACnD,OAAO;QACL,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC9D,CAAC;IAED,sCAAsC;IAC9B,MAAM,CAAC,GAAW;QACxB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACrC,IAAI,CAAC,OAAO;YAAE,OAAM;QAEpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACxB,OAAO,CAAC,MAAM,EAAE,CAAA;QAChB,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IAC3B,CAAC;CACF;AAED,+DAA+D;AAC/D,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,157 @@
1
+ /**
2
+ * Fetching a message's media and turning it into prompt content.
3
+ *
4
+ * Downloading is the part that can go wrong in ordinary ways — a file above
5
+ * the bot download limit, a network drop, an image the harness refuses — and
6
+ * none of them should cost the user their message. So every failure becomes a
7
+ * note in the prompt saying what could not be read, and the text the user
8
+ * typed alongside it still reaches the agent.
9
+ */
10
+ import type { TelegramMessage } from '../telegram/types.js';
11
+ import type { Logger } from '../harness/types.js';
12
+ import type { ImageLimits } from './limits.js';
13
+ import type { VisionCheck } from './vision.js';
14
+ /** A durable image reference, as the harness attachment seam returns it. */
15
+ export interface ImageRef {
16
+ readonly attachmentId: unknown;
17
+ readonly mediaType: string;
18
+ readonly bytes: number;
19
+ readonly width: number;
20
+ readonly height: number;
21
+ readonly name?: string;
22
+ }
23
+ /** One piece of a prompt. */
24
+ export type PromptPart = {
25
+ readonly type: 'text';
26
+ readonly text: string;
27
+ } | {
28
+ readonly type: 'image';
29
+ readonly attachment: ImageRef;
30
+ };
31
+ /** Downloading bytes from Telegram. */
32
+ export interface MediaSource {
33
+ getFile(fileId: string): Promise<{
34
+ file_path?: string;
35
+ file_size?: number;
36
+ }>;
37
+ downloadFile(filePath: string, signal?: AbortSignal): Promise<Uint8Array>;
38
+ }
39
+ /** The harness attachment seam, narrowed to what this needs. */
40
+ export interface AttachmentStore {
41
+ saveImage(input: {
42
+ data: Uint8Array;
43
+ mediaType: string;
44
+ name?: string;
45
+ }): Promise<ImageRef>;
46
+ /**
47
+ * The seam's own limits, where it publishes them.
48
+ *
49
+ * Read rather than configured a second time: the operator sets this on the
50
+ * attachment plugin, and a copy on this side would silently drift out of
51
+ * step with the number that actually decides.
52
+ */
53
+ readonly imageLimits?: ImageLimits;
54
+ }
55
+ /** What the caller knows about the turn these attachments are for. */
56
+ export interface CollectionContext {
57
+ /**
58
+ * Whether the model this turn will run on accepts images itself.
59
+ *
60
+ * Decided per conversation, because `/model` is per conversation: judging
61
+ * the deployment default would refuse an image on a chat that had switched
62
+ * to a model which can see.
63
+ */
64
+ readonly modelSees?: boolean;
65
+ }
66
+ /** What a message's attachments became, plus anything the user should be told. */
67
+ export interface CollectedMedia {
68
+ /** Content for the agent, in the order the model should read it. */
69
+ readonly parts: PromptPart[];
70
+ /**
71
+ * A line for the chat, when something the user sent could not be used. The
72
+ * agent's prompt says so too, but the person who sent it should not have to
73
+ * wait for a reply to learn their screenshot went nowhere.
74
+ */
75
+ readonly notice?: string;
76
+ }
77
+ /** Construction options. */
78
+ export interface MediaCollectorOptions {
79
+ readonly source: MediaSource;
80
+ /** Absent on a deployment with no attachment seam; images are then declined. */
81
+ readonly attachments?: AttachmentStore;
82
+ /**
83
+ * Whether the model can read an image at all. Absent skips the check and
84
+ * lets the provider be the authority, which is the older behaviour.
85
+ */
86
+ readonly vision?: VisionCheck;
87
+ /**
88
+ * Whether an image can be read even where no model accepts one — OCR.
89
+ *
90
+ * Consulted before refusing, because a refusal here happens before the
91
+ * download and so takes the picture away from whatever could have read it.
92
+ */
93
+ readonly canReadWithoutModel?: () => Promise<boolean>;
94
+ /** Refuse anything larger, before downloading it. */
95
+ readonly maxBytes: number;
96
+ /** Truncate an inlined text file to this many characters. */
97
+ readonly maxTextChars: number;
98
+ /**
99
+ * Strip anything secret from a refusal before it is written down.
100
+ *
101
+ * A refusal quotes the failure that caused it, and lands in two durable
102
+ * places at once: the chat, and the session log the agent carries forward.
103
+ * The API client already redacts its own errors, so this is the second lock
104
+ * rather than the first — but this sink is the one that persists.
105
+ */
106
+ readonly redact?: (text: string) => string;
107
+ readonly logger?: Logger;
108
+ }
109
+ export declare class MediaCollector {
110
+ private readonly options;
111
+ private readonly logger;
112
+ constructor(options: MediaCollectorOptions);
113
+ /**
114
+ * Turn one message into the parts a prompt is built from.
115
+ *
116
+ * @param message - the incoming message.
117
+ * @param caption - the text the user typed, if any.
118
+ * @returns text and image parts, in the order the model should read them.
119
+ */
120
+ collect(message: TelegramMessage, caption: string | undefined, context?: CollectionContext): Promise<CollectedMedia>;
121
+ /**
122
+ * Turn a whole album into one prompt.
123
+ *
124
+ * Each part is collected on its own — the same path a single photo takes, so
125
+ * refusals and size limits behave identically — and the results are then
126
+ * laid out as one message would have been: what the user said, then every
127
+ * image, then anything that could not be read.
128
+ *
129
+ * @param messages - the album's parts, in the order they were sent.
130
+ * @param caption - the one caption the album carries.
131
+ */
132
+ collectAll(messages: readonly TelegramMessage[], caption: string | undefined, context?: CollectionContext): Promise<CollectedMedia>;
133
+ /**
134
+ * Store an image, stepping down through Telegram's smaller renderings.
135
+ *
136
+ * The seam refuses anything over its per-side limit, and the largest size of
137
+ * a phone screenshot is always over it — 1179×2556 against a limit of 2000.
138
+ * The largest size that fits is tried first, and a rejection falls through
139
+ * to the next, because the limit belongs to the harness and may not be the
140
+ * number read here.
141
+ *
142
+ * @returns the stored reference, or undefined when no size was accepted.
143
+ */
144
+ private storeImage;
145
+ /**
146
+ * Why an image must not be sent on the current route, if it must not.
147
+ *
148
+ * A model with no image input rejects the entire request, so this refusal
149
+ * replaces a failed turn with a sentence naming what would have worked.
150
+ */
151
+ private imageRefusal;
152
+ /** Keep the caption, tell the agent what was left out, and tell the user why. */
153
+ private declined;
154
+ /** Resolve a file id and pull its bytes, refusing anything oversized. */
155
+ private download;
156
+ }
157
+ //# sourceMappingURL=collect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../src/media/collect.ts"],"names":[],"mappings":"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"}