@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,101 @@
1
+ /**
2
+ * Taking a picture of the screen the harness is running on.
3
+ *
4
+ * Useful for the same reason the bot exists at all: the machine is at a desk
5
+ * and you are not. Checking what a long build is showing, or what that dialog
6
+ * says, is otherwise a trip back to the keyboard.
7
+ *
8
+ * It is also the one thing this plugin does that sends the machine's own
9
+ * contents outward without the agent being involved, and a screen holds
10
+ * whatever happens to be on it — an open password manager, someone else's
11
+ * messages, an unrelated customer's data. So it is off unless switched on, and
12
+ * the switch is deliberately a deployment setting rather than a chat command:
13
+ * turning it on should take the same access as configuring the bot.
14
+ *
15
+ * macOS also requires Screen Recording permission for the process that runs
16
+ * the harness. Without it `screencapture` succeeds and returns the desktop
17
+ * picture with no windows, which looks like a broken feature rather than a
18
+ * missing permission — so that case is named rather than shrugged at.
19
+ */
20
+ import { execFile } from 'node:child_process';
21
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
22
+ import { tmpdir } from 'node:os';
23
+ import { join } from 'node:path';
24
+ import { SILENT_LOGGER } from '../harness/types.js';
25
+ /** How long to wait for the capture before giving up. */
26
+ const DEFAULT_TIMEOUT_MS = 20_000;
27
+ /** Telegram refuses a photo above this; a document goes up to 50 MB. */
28
+ export const PHOTO_LIMIT_BYTES = 10 * 1024 * 1024;
29
+ export class Screenshotter {
30
+ options;
31
+ logger;
32
+ constructor(options = {}) {
33
+ this.options = options;
34
+ this.logger = options.logger ?? SILENT_LOGGER;
35
+ }
36
+ /** Whether this platform has a capture tool this knows how to drive. */
37
+ get available() {
38
+ return (this.options.platform ?? process.platform) === 'darwin' || this.options.run !== undefined;
39
+ }
40
+ /**
41
+ * Capture the screen.
42
+ *
43
+ * @returns the image, or why there is none.
44
+ */
45
+ async take() {
46
+ const platform = this.options.platform ?? process.platform;
47
+ if (!this.available)
48
+ return { kind: 'unsupported', platform };
49
+ // Written to a directory of its own so the file cannot collide with
50
+ // another capture, and so one removal cleans up whatever was produced.
51
+ const directory = await mkdtemp(join(tmpdir(), 'dsh-telegram-shot-'));
52
+ const output = join(directory, 'screen.png');
53
+ const controller = new AbortController();
54
+ const timer = setTimeout(() => controller.abort(), this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
55
+ try {
56
+ await (this.options.run ?? captureWithScreencapture)(output, controller.signal);
57
+ const data = await readFile(output);
58
+ if (data.length === 0)
59
+ return { kind: 'failed', reason: 'the capture came back empty' };
60
+ return { kind: 'image', data, filename: 'screen.png' };
61
+ }
62
+ catch (error) {
63
+ return { kind: 'failed', reason: describe(error, controller.signal.aborted) };
64
+ }
65
+ finally {
66
+ clearTimeout(timer);
67
+ await rm(directory, { recursive: true, force: true }).catch((error) => {
68
+ this.logger.warn('[dsh-telegram] could not remove a screenshot', error);
69
+ });
70
+ }
71
+ }
72
+ }
73
+ /** Drive macOS's own `screencapture`. */
74
+ function captureWithScreencapture(output, signal) {
75
+ return new Promise((resolve, reject) => {
76
+ // -x is silent: a bot taking a picture should not play the camera sound at
77
+ // whoever is sitting there. -C includes the cursor, which is often the
78
+ // point when someone asks what is on screen.
79
+ execFile('/usr/sbin/screencapture', ['-x', '-C', output], { signal }, (error) => {
80
+ if (error)
81
+ reject(error);
82
+ else
83
+ resolve();
84
+ });
85
+ });
86
+ }
87
+ /** Turn whatever went wrong into something a person can act on. */
88
+ function describe(error, timedOut) {
89
+ if (timedOut)
90
+ return 'the capture took too long';
91
+ const message = error instanceof Error ? error.message : String(error);
92
+ if (/ENOENT/.test(message))
93
+ return 'no screen capture tool was found';
94
+ // The permission case does not fail loudly on macOS — it quietly returns the
95
+ // desktop picture — so this covers the version that does.
96
+ if (/not permitted|denied/i.test(message)) {
97
+ return 'the harness has no Screen Recording permission';
98
+ }
99
+ return message;
100
+ }
101
+ //# sourceMappingURL=screenshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../src/media/screenshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Asking whether the model can look at a picture before sending it one.
3
+ *
4
+ * A model that takes no image input rejects the whole request, so an
5
+ * unchecked screenshot does not degrade — it fails the turn, and the user is
6
+ * told their model "does not accept image input" by a layer that knows nothing
7
+ * about Telegram or about what they were trying to do.
8
+ *
9
+ * The harness already publishes the answer: every model carries
10
+ * `inputModalities`, defaulting to text alone. Reading it first turns a failed
11
+ * turn into a sentence naming the models that would have worked.
12
+ *
13
+ * The check is best-effort in one direction only. A definite "no" refuses the
14
+ * image; anything else — the catalog is unreachable, the model is unknown —
15
+ * sends it and lets the provider be the authority, because a check that cannot
16
+ * answer must not remove a capability the model may well have.
17
+ */
18
+ /** What the harness knows about one model. */
19
+ export interface ModelInfo {
20
+ readonly id: string;
21
+ readonly name?: string;
22
+ readonly inputModalities?: readonly string[];
23
+ }
24
+ /** The `ctx.llm` surface this needs. */
25
+ export interface ModelCatalog {
26
+ resolveModelInfo(provider: string, model: string): Promise<ModelInfo>;
27
+ listModels(provider: string): Promise<readonly ModelInfo[]>;
28
+ }
29
+ /** The route a conversation's agent is running on. */
30
+ export interface ModelRoute {
31
+ readonly provider: string;
32
+ readonly model: string;
33
+ }
34
+ /** Whether a model declares image input. */
35
+ export declare function acceptsImages(info: ModelInfo | undefined): boolean;
36
+ export declare class VisionCheck {
37
+ private readonly catalog;
38
+ /** The route in force right now, read late so a model change is picked up. */
39
+ private readonly route;
40
+ constructor(catalog: ModelCatalog,
41
+ /** The route in force right now, read late so a model change is picked up. */
42
+ route: () => ModelRoute | undefined);
43
+ /**
44
+ * Whether an image may be sent on the current route.
45
+ *
46
+ * @returns `'yes'` when the model declares image input, `'no'` when it
47
+ * definitely does not, and `'unknown'` when the catalog could not say —
48
+ * in which case the caller should send and let the provider decide.
49
+ */
50
+ verdict(): Promise<'yes' | 'no' | 'unknown'>;
51
+ /**
52
+ * Models on the current provider that would accept the image.
53
+ *
54
+ * @returns their display names, or an empty list when none or unknown.
55
+ */
56
+ alternatives(): Promise<string[]>;
57
+ /** The model an image would have been sent to, for naming it in a message. */
58
+ currentModel(): string | undefined;
59
+ }
60
+ //# sourceMappingURL=vision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vision.d.ts","sourceRoot":"","sources":["../../src/media/vision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,8CAA8C;AAC9C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC7C;AAED,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IACrE,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAAA;CAC5D;AAED,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,4CAA4C;AAC5C,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAElE;AAED,qBAAa,WAAW;IAEpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,8EAA8E;IAC9E,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAFL,OAAO,EAAE,YAAY;IACtC,8EAA8E;IAC7D,KAAK,EAAE,MAAM,UAAU,GAAG,SAAS;IAGtD;;;;;;OAMG;IACG,OAAO,IAAI,OAAO,CAAC,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC;IAalD;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAYvC,8EAA8E;IAC9E,YAAY,IAAI,MAAM,GAAG,SAAS;CAGnC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Asking whether the model can look at a picture before sending it one.
3
+ *
4
+ * A model that takes no image input rejects the whole request, so an
5
+ * unchecked screenshot does not degrade — it fails the turn, and the user is
6
+ * told their model "does not accept image input" by a layer that knows nothing
7
+ * about Telegram or about what they were trying to do.
8
+ *
9
+ * The harness already publishes the answer: every model carries
10
+ * `inputModalities`, defaulting to text alone. Reading it first turns a failed
11
+ * turn into a sentence naming the models that would have worked.
12
+ *
13
+ * The check is best-effort in one direction only. A definite "no" refuses the
14
+ * image; anything else — the catalog is unreachable, the model is unknown —
15
+ * sends it and lets the provider be the authority, because a check that cannot
16
+ * answer must not remove a capability the model may well have.
17
+ */
18
+ /** Whether a model declares image input. */
19
+ export function acceptsImages(info) {
20
+ return info?.inputModalities?.includes('image') === true;
21
+ }
22
+ export class VisionCheck {
23
+ catalog;
24
+ route;
25
+ constructor(catalog,
26
+ /** The route in force right now, read late so a model change is picked up. */
27
+ route) {
28
+ this.catalog = catalog;
29
+ this.route = route;
30
+ }
31
+ /**
32
+ * Whether an image may be sent on the current route.
33
+ *
34
+ * @returns `'yes'` when the model declares image input, `'no'` when it
35
+ * definitely does not, and `'unknown'` when the catalog could not say —
36
+ * in which case the caller should send and let the provider decide.
37
+ */
38
+ async verdict() {
39
+ const route = this.route();
40
+ if (!route)
41
+ return 'unknown';
42
+ try {
43
+ const info = await this.catalog.resolveModelInfo(route.provider, route.model);
44
+ if (info.inputModalities === undefined)
45
+ return 'unknown';
46
+ return acceptsImages(info) ? 'yes' : 'no';
47
+ }
48
+ catch {
49
+ return 'unknown';
50
+ }
51
+ }
52
+ /**
53
+ * Models on the current provider that would accept the image.
54
+ *
55
+ * @returns their display names, or an empty list when none or unknown.
56
+ */
57
+ async alternatives() {
58
+ const route = this.route();
59
+ if (!route)
60
+ return [];
61
+ try {
62
+ const models = await this.catalog.listModels(route.provider);
63
+ return models.filter(acceptsImages).map((model) => model.name ?? model.id);
64
+ }
65
+ catch {
66
+ return [];
67
+ }
68
+ }
69
+ /** The model an image would have been sent to, for naming it in a message. */
70
+ currentModel() {
71
+ return this.route()?.model;
72
+ }
73
+ }
74
+ //# sourceMappingURL=vision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vision.js","sourceRoot":"","sources":["../../src/media/vision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAqBH,4CAA4C;AAC5C,MAAM,UAAU,aAAa,CAAC,IAA2B;IACvD,OAAO,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;AAC1D,CAAC;AAED,MAAM,OAAO,WAAW;IAEH;IAEA;IAHnB,YACmB,OAAqB;IACtC,8EAA8E;IAC7D,KAAmC;QAFnC,YAAO,GAAP,OAAO,CAAc;QAErB,UAAK,GAAL,KAAK,CAA8B;IACnD,CAAC;IAEJ;;;;;;OAMG;IACH,KAAK,CAAC,OAAO;QACX,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAA;QAC1B,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAA;QAE5B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;YAC7E,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAA;YACxD,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAA;QAClB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAA;QAC1B,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,CAAA;QAErB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;YAC5D,OAAO,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,CAAA;QAC5E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,YAAY;QACV,OAAO,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,CAAA;IAC5B,CAAC;CACF"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Keeping the plugin's own prompts inside Telegram's message limit.
3
+ *
4
+ * Agent replies go out as rich messages and get 32768 characters. The prompts
5
+ * this plugin assembles — an approval, a question, a status line — stay on
6
+ * plain HTML and get 4096, and they embed model-authored text of no fixed
7
+ * length: a tool's stated reason, a question's option descriptions.
8
+ *
9
+ * Left unbounded, a verbose reason pushes the message past the limit, the send
10
+ * fails, and the approval fails closed — the agent is refused permission it
11
+ * was never actually asked about, and nobody sees a button. Bounding the
12
+ * inputs is what keeps that from being possible.
13
+ *
14
+ * Escaping is the subtlety: `&` becomes `&amp;`, so escaped text can be five
15
+ * times longer than its source. Cutting after escaping risks splitting an
16
+ * entity, so the text is cut first and escaped afterwards, shrinking until the
17
+ * escaped form fits.
18
+ */
19
+ /**
20
+ * Escape text, cutting it down until the escaped form fits.
21
+ *
22
+ * @param text - raw, possibly model-authored text.
23
+ * @param maxEscaped - the budget in characters of the escaped result.
24
+ * @returns escaped html no longer than `maxEscaped`.
25
+ */
26
+ export declare function escapeWithin(text: string, maxEscaped: number): string;
27
+ /**
28
+ * Cut raw text to a length, on a word boundary where one is close by.
29
+ *
30
+ * @param text - the raw text.
31
+ * @param max - maximum characters.
32
+ */
33
+ export declare function clamp(text: string, max: number): string;
34
+ //# sourceMappingURL=clamp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clamp.d.ts","sourceRoot":"","sources":["../../src/render/clamp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAkBrE;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGvD"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Keeping the plugin's own prompts inside Telegram's message limit.
3
+ *
4
+ * Agent replies go out as rich messages and get 32768 characters. The prompts
5
+ * this plugin assembles — an approval, a question, a status line — stay on
6
+ * plain HTML and get 4096, and they embed model-authored text of no fixed
7
+ * length: a tool's stated reason, a question's option descriptions.
8
+ *
9
+ * Left unbounded, a verbose reason pushes the message past the limit, the send
10
+ * fails, and the approval fails closed — the agent is refused permission it
11
+ * was never actually asked about, and nobody sees a button. Bounding the
12
+ * inputs is what keeps that from being possible.
13
+ *
14
+ * Escaping is the subtlety: `&` becomes `&amp;`, so escaped text can be five
15
+ * times longer than its source. Cutting after escaping risks splitting an
16
+ * entity, so the text is cut first and escaped afterwards, shrinking until the
17
+ * escaped form fits.
18
+ */
19
+ import { escapeHtml } from './escape.js';
20
+ /** Marker showing the reader that text was cut. */
21
+ const ELLIPSIS = '…';
22
+ /**
23
+ * Escape text, cutting it down until the escaped form fits.
24
+ *
25
+ * @param text - raw, possibly model-authored text.
26
+ * @param maxEscaped - the budget in characters of the escaped result.
27
+ * @returns escaped html no longer than `maxEscaped`.
28
+ */
29
+ export function escapeWithin(text, maxEscaped) {
30
+ if (maxEscaped <= 0)
31
+ return '';
32
+ const escaped = escapeHtml(text);
33
+ if (escaped.length <= maxEscaped)
34
+ return escaped;
35
+ // Shrink proportionally rather than character by character: escaping expands
36
+ // by a factor, so one or two passes converge where a linear walk would take
37
+ // thousands.
38
+ let cut = text;
39
+ for (let attempt = 0; attempt < 8 && cut.length > 1; attempt += 1) {
40
+ const current = escapeHtml(cut);
41
+ if (current.length <= maxEscaped - ELLIPSIS.length)
42
+ break;
43
+ const ratio = (maxEscaped - ELLIPSIS.length) / current.length;
44
+ cut = cut.slice(0, Math.max(1, Math.floor(cut.length * ratio) - 1));
45
+ }
46
+ return `${escapeHtml(trimTail(cut))}${ELLIPSIS}`;
47
+ }
48
+ /**
49
+ * Cut raw text to a length, on a word boundary where one is close by.
50
+ *
51
+ * @param text - the raw text.
52
+ * @param max - maximum characters.
53
+ */
54
+ export function clamp(text, max) {
55
+ if (text.length <= max)
56
+ return text;
57
+ return `${trimTail(text.slice(0, Math.max(1, max - ELLIPSIS.length)))}${ELLIPSIS}`;
58
+ }
59
+ /** Drop a partial trailing word and any whitespace before the marker. */
60
+ function trimTail(text) {
61
+ const lastSpace = text.lastIndexOf(' ');
62
+ const trimmed = lastSpace > text.length * 0.6 ? text.slice(0, lastSpace) : text;
63
+ return trimmed.trimEnd();
64
+ }
65
+ //# sourceMappingURL=clamp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clamp.js","sourceRoot":"","sources":["../../src/render/clamp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,mDAAmD;AACnD,MAAM,QAAQ,GAAG,GAAG,CAAA;AAEpB;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,UAAkB;IAC3D,IAAI,UAAU,IAAI,CAAC;QAAE,OAAO,EAAE,CAAA;IAE9B,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAChC,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;QAAE,OAAO,OAAO,CAAA;IAEhD,6EAA6E;IAC7E,4EAA4E;IAC5E,aAAa;IACb,IAAI,GAAG,GAAG,IAAI,CAAA;IACd,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAClE,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;QAC/B,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU,GAAG,QAAQ,CAAC,MAAM;YAAE,MAAK;QACzD,MAAM,KAAK,GAAG,CAAC,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAA;QAC7D,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IACrE,CAAC;IAED,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,EAAE,CAAA;AAClD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CAAC,IAAY,EAAE,GAAW;IAC7C,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,IAAI,CAAA;IACnC,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,EAAE,CAAA;AACpF,CAAC;AAED,yEAAyE;AACzE,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IACvC,MAAM,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC/E,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;AAC1B,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * HTML escaping for the plugin's own messages.
3
+ *
4
+ * Agent replies are sent as rich markdown for Telegram to render, but the
5
+ * prompts this plugin writes itself — questions, approvals, command answers —
6
+ * stay on HTML: they are short, they carry inline keyboards, and they embed
7
+ * model-authored text that must not be able to introduce markup.
8
+ *
9
+ * Telegram's HTML mode is not a browser: it recognises a fixed tag whitelist
10
+ * and treats everything else as text. Only `&`, `<` and `>` can start markup,
11
+ * so those are the only three characters that must be escaped.
12
+ */
13
+ /** Escape text content so Telegram reads it as text, never as markup. */
14
+ export declare function escapeHtml(text: string): string;
15
+ //# sourceMappingURL=escape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape.d.ts","sourceRoot":"","sources":["../../src/render/escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * HTML escaping for the plugin's own messages.
3
+ *
4
+ * Agent replies are sent as rich markdown for Telegram to render, but the
5
+ * prompts this plugin writes itself — questions, approvals, command answers —
6
+ * stay on HTML: they are short, they carry inline keyboards, and they embed
7
+ * model-authored text that must not be able to introduce markup.
8
+ *
9
+ * Telegram's HTML mode is not a browser: it recognises a fixed tag whitelist
10
+ * and treats everything else as text. Only `&`, `<` and `>` can start markup,
11
+ * so those are the only three characters that must be escaped.
12
+ */
13
+ /** Escape text content so Telegram reads it as text, never as markup. */
14
+ export function escapeHtml(text) {
15
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
16
+ }
17
+ //# sourceMappingURL=escape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape.js","sourceRoot":"","sources":["../../src/render/escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,yEAAyE;AACzE,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AAChF,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Saying what the agent is doing right now.
3
+ *
4
+ * A turn that spends two minutes running tests emits no text, so the reply
5
+ * shows a placeholder and nothing else — from the outside that is
6
+ * indistinguishable from a bot that has died. The tool calls are already in
7
+ * the session feed; they were simply being dropped.
8
+ *
9
+ * What is shown is deliberately one short line. A tool call's arguments can be
10
+ * an entire file, and the point is reassurance, not a transcript: the finished
11
+ * reply carries the substance, and this disappears when the turn ends.
12
+ */
13
+ /**
14
+ * Describe one tool call in a line.
15
+ *
16
+ * @param name - the tool the model invoked.
17
+ * @param argumentsJson - the raw arguments string, exactly as the model wrote
18
+ * it, which means it may be incomplete or not be JSON at all.
19
+ * @returns a short line, already escaped for the markup it is placed in.
20
+ */
21
+ export declare function describeToolCall(name: string, argumentsJson: string | undefined): string;
22
+ /**
23
+ * Wrap an activity line in the block Telegram shows as "thinking".
24
+ *
25
+ * The tag is valid in rich markdown and rich HTML alike, and is accepted only
26
+ * in a draft — which is exactly its lifetime here.
27
+ *
28
+ * @param activity - the escaped line, or undefined to show nothing.
29
+ */
30
+ export declare function thinkingBlock(activity: string | undefined): string;
31
+ //# sourceMappingURL=activity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../../src/reply/activity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA0BH;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAIxF;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGlE"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Saying what the agent is doing right now.
3
+ *
4
+ * A turn that spends two minutes running tests emits no text, so the reply
5
+ * shows a placeholder and nothing else — from the outside that is
6
+ * indistinguishable from a bot that has died. The tool calls are already in
7
+ * the session feed; they were simply being dropped.
8
+ *
9
+ * What is shown is deliberately one short line. A tool call's arguments can be
10
+ * an entire file, and the point is reassurance, not a transcript: the finished
11
+ * reply carries the substance, and this disappears when the turn ends.
12
+ */
13
+ import { escapeHtml } from '../render/escape.js';
14
+ /** Longest activity line shown; a command can be arbitrarily long. */
15
+ const MAX_DETAIL = 80;
16
+ /**
17
+ * Argument fields worth showing, in the order they are looked for.
18
+ *
19
+ * A tool's most identifying argument is almost always one of these, and
20
+ * guessing from the first key present beats rendering the whole JSON.
21
+ */
22
+ const DETAIL_KEYS = [
23
+ 'command',
24
+ 'cmd',
25
+ 'file_path',
26
+ 'path',
27
+ 'filePath',
28
+ 'pattern',
29
+ 'query',
30
+ 'url',
31
+ 'description',
32
+ 'name',
33
+ ];
34
+ /**
35
+ * Describe one tool call in a line.
36
+ *
37
+ * @param name - the tool the model invoked.
38
+ * @param argumentsJson - the raw arguments string, exactly as the model wrote
39
+ * it, which means it may be incomplete or not be JSON at all.
40
+ * @returns a short line, already escaped for the markup it is placed in.
41
+ */
42
+ export function describeToolCall(name, argumentsJson) {
43
+ const detail = firstDetail(argumentsJson);
44
+ const label = detail === undefined ? name : `${name}: ${detail}`;
45
+ return escapeHtml(clip(label, MAX_DETAIL));
46
+ }
47
+ /**
48
+ * Wrap an activity line in the block Telegram shows as "thinking".
49
+ *
50
+ * The tag is valid in rich markdown and rich HTML alike, and is accepted only
51
+ * in a draft — which is exactly its lifetime here.
52
+ *
53
+ * @param activity - the escaped line, or undefined to show nothing.
54
+ */
55
+ export function thinkingBlock(activity) {
56
+ if (activity === undefined || activity === '')
57
+ return '';
58
+ return `<tg-thinking>${activity}</tg-thinking>`;
59
+ }
60
+ /** The first recognisable argument, or undefined when none can be read. */
61
+ function firstDetail(argumentsJson) {
62
+ if (!argumentsJson)
63
+ return undefined;
64
+ let parsed;
65
+ try {
66
+ parsed = JSON.parse(argumentsJson);
67
+ }
68
+ catch {
69
+ // Arguments stream in, so mid-call they are routinely incomplete JSON.
70
+ return undefined;
71
+ }
72
+ if (typeof parsed !== 'object' || parsed === null)
73
+ return undefined;
74
+ const record = parsed;
75
+ for (const key of DETAIL_KEYS) {
76
+ const value = record[key];
77
+ if (typeof value === 'string' && value.trim() !== '')
78
+ return value.trim().split('\n')[0];
79
+ }
80
+ return undefined;
81
+ }
82
+ /** Cut a line to length, marking that it was cut. */
83
+ function clip(text, max) {
84
+ if (text.length <= max)
85
+ return text;
86
+ return `${text.slice(0, max - 1)}…`;
87
+ }
88
+ //# sourceMappingURL=activity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.js","sourceRoot":"","sources":["../../src/reply/activity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAEhD,sEAAsE;AACtE,MAAM,UAAU,GAAG,EAAE,CAAA;AAErB;;;;;GAKG;AACH,MAAM,WAAW,GAAG;IAClB,SAAS;IACT,KAAK;IACL,WAAW;IACX,MAAM;IACN,UAAU;IACV,SAAS;IACT,OAAO;IACP,KAAK;IACL,aAAa;IACb,MAAM;CACE,CAAA;AAEV;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,aAAiC;IAC9E,MAAM,MAAM,GAAG,WAAW,CAAC,aAAa,CAAC,CAAA;IACzC,MAAM,KAAK,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,MAAM,EAAE,CAAA;IAChE,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,QAA4B;IACxD,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE;QAAE,OAAO,EAAE,CAAA;IACxD,OAAO,gBAAgB,QAAQ,gBAAgB,CAAA;AACjD,CAAC;AAED,2EAA2E;AAC3E,SAAS,WAAW,CAAC,aAAiC;IACpD,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAA;IAEpC,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,uEAAuE;QACvE,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IACnE,MAAM,MAAM,GAAG,MAAiC,CAAA;IAEhD,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;QACzB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1F,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,qDAAqD;AACrD,SAAS,IAAI,CAAC,IAAY,EAAE,GAAW;IACrC,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,IAAI,CAAA;IACnC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,CAAA;AACrC,CAAC"}