@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,115 @@
1
+ /**
2
+ * Getting what the user sent into what the agent can read.
3
+ *
4
+ * Three kinds arrive and only two can go anywhere useful:
5
+ *
6
+ * - **Images** reach the model. The harness attachment seam accepts PNG,
7
+ * JPEG, WebP and GIF, commits the bytes durably, and hands back a reference
8
+ * the session log can carry — which is why a screenshot works at all.
9
+ * - **Text documents** — a log, a stack trace, a source file — have no seam,
10
+ * but they do not need one: their content is text, so it is read and placed
11
+ * in the prompt where the model already looks.
12
+ * - **Voice, audio and video** have neither. The harness explicitly defers
13
+ * them, so the honest answer is to say so rather than to accept the message
14
+ * and silently drop what it carried.
15
+ *
16
+ * Telegram sends an uncompressed image as a *document*, so kind is decided by
17
+ * media type rather than by which field it arrived in.
18
+ */
19
+ /** Media types the harness attachment seam accepts. */
20
+ const IMAGE_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/gif']);
21
+ /** Media types worth reading as text, beyond anything named `text/*`. */
22
+ const TEXT_TYPES = new Set([
23
+ 'application/json',
24
+ 'application/xml',
25
+ 'application/x-yaml',
26
+ 'application/yaml',
27
+ 'application/javascript',
28
+ 'application/typescript',
29
+ 'application/sql',
30
+ 'application/toml',
31
+ ]);
32
+ /** Extensions that are text regardless of the type Telegram guessed. */
33
+ const TEXT_EXTENSIONS = new Set([
34
+ 'txt', 'md', 'log', 'json', 'yaml', 'yml', 'toml', 'ini', 'cfg', 'conf', 'env',
35
+ 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'py', 'rb', 'go', 'rs', 'java', 'kt',
36
+ 'c', 'h', 'cpp', 'hpp', 'cs', 'php', 'swift', 'sh', 'bash', 'zsh', 'sql',
37
+ 'html', 'css', 'scss', 'xml', 'csv', 'diff', 'patch', 'lock',
38
+ ]);
39
+ /**
40
+ * Identify the media a message carries.
41
+ *
42
+ * @param message - the incoming Telegram message.
43
+ * @returns the item, or undefined for a message carrying none.
44
+ */
45
+ export function describeMedia(message) {
46
+ if (message.photo && message.photo.length > 0) {
47
+ // Every size is kept. The largest used to be taken outright, which is why
48
+ // any full-height phone screenshot was refused: its long side is over the
49
+ // seam's 2000-pixel limit, and a smaller rendering of the same photo was
50
+ // sitting right there in the same message.
51
+ const candidates = message.photo.map((size) => ({
52
+ fileId: size.file_id,
53
+ ...(size.width !== undefined ? { width: size.width } : {}),
54
+ ...(size.height !== undefined ? { height: size.height } : {}),
55
+ ...(size.file_size !== undefined ? { size: size.file_size } : {}),
56
+ }));
57
+ const largest = candidates[candidates.length - 1];
58
+ if (!largest)
59
+ return undefined;
60
+ return {
61
+ fileId: largest.fileId,
62
+ kind: 'image',
63
+ mediaType: 'image/jpeg',
64
+ ...(largest.size !== undefined ? { size: largest.size } : {}),
65
+ candidates,
66
+ };
67
+ }
68
+ if (message.document) {
69
+ const { file_id: fileId, file_name: name, mime_type: mediaType } = message.document;
70
+ const kind = documentKind(mediaType, name);
71
+ return {
72
+ fileId,
73
+ kind,
74
+ ...(mediaType !== undefined ? { mediaType } : {}),
75
+ ...(name !== undefined ? { name } : {}),
76
+ describedAs: mediaType ?? 'file',
77
+ // One size only: Telegram renders nothing for a file sent uncompressed,
78
+ // which is exactly why sending a screenshot that way can fail where
79
+ // sending it as a photo succeeds.
80
+ ...(kind === 'image' ? { candidates: [{ fileId }] } : {}),
81
+ };
82
+ }
83
+ if (message.voice) {
84
+ return { fileId: message.voice.file_id, kind: 'unsupported', describedAs: 'a voice note' };
85
+ }
86
+ if (message.audio) {
87
+ return { fileId: message.audio.file_id, kind: 'unsupported', describedAs: 'an audio file' };
88
+ }
89
+ if (message.video) {
90
+ return { fileId: message.video.file_id, kind: 'unsupported', describedAs: 'a video' };
91
+ }
92
+ return undefined;
93
+ }
94
+ /** Whether a document is an image, readable text, or neither. */
95
+ function documentKind(mediaType, name) {
96
+ if (mediaType && IMAGE_TYPES.has(mediaType))
97
+ return 'image';
98
+ if (mediaType && (mediaType.startsWith('text/') || TEXT_TYPES.has(mediaType)))
99
+ return 'text';
100
+ // Telegram often guesses application/octet-stream for source files, so the
101
+ // extension is the better signal when the type says nothing useful.
102
+ const extension = name?.split('.').pop()?.toLowerCase();
103
+ if (extension && TEXT_EXTENSIONS.has(extension))
104
+ return 'text';
105
+ return 'unsupported';
106
+ }
107
+ /**
108
+ * Whether the harness can store this image.
109
+ *
110
+ * @param mediaType - the type Telegram reported.
111
+ */
112
+ export function isStorableImage(mediaType) {
113
+ return mediaType !== undefined && IMAGE_TYPES.has(mediaType);
114
+ }
115
+ //# sourceMappingURL=intake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intake.js","sourceRoot":"","sources":["../../src/media/intake.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Choosing which size of a photo to store.
3
+ *
4
+ * The harness attachment seam refuses an image whose longest side is over
5
+ * `maxImageDimension` — 2000 pixels by default. Every modern phone screenshot
6
+ * is over it: 1179×2556 on an iPhone, 1080×2400 on most Android. Taking the
7
+ * largest size Telegram offers, which is what "best quality" naively means,
8
+ * therefore fails for the single most common thing anyone sends a bot.
9
+ *
10
+ * Telegram already solves this. A photo arrives as several rendered sizes, and
11
+ * one of the smaller ones is almost always both within the limit and far more
12
+ * than a model needs to read a receipt. So the largest size that fits is the
13
+ * one to send, and the rest are kept as fallbacks: the limit is the harness's
14
+ * to change, so a rejection is answered by stepping down rather than by
15
+ * trusting the number this side read.
16
+ */
17
+ /** One rendered size of an image, as Telegram offers it. */
18
+ export interface ImageCandidate {
19
+ readonly fileId: string;
20
+ readonly width?: number;
21
+ readonly height?: number;
22
+ readonly size?: number;
23
+ }
24
+ /** The attachment seam's own limits, where it publishes them. */
25
+ export interface ImageLimits {
26
+ readonly maxImageDimension?: number;
27
+ readonly maxImagePixels?: number;
28
+ }
29
+ /**
30
+ * What to assume when the seam publishes nothing.
31
+ *
32
+ * The harness's own default, so a store that keeps its limits to itself is
33
+ * still met with a sensible guess rather than with the smallest thumbnail
34
+ * Telegram has — which is 90 pixels wide and legible to nobody. A guess is
35
+ * safe here because it only decides what to try first: a refusal steps down.
36
+ */
37
+ export declare const ASSUMED_IMAGE_LIMITS: ImageLimits;
38
+ /**
39
+ * Order candidates by which to try first.
40
+ *
41
+ * Everything known to fit comes first, largest of those leading, because a
42
+ * bigger image is easier to read. Everything else follows in ascending order,
43
+ * so a blind attempt starts with the one most likely to be accepted.
44
+ *
45
+ * @param candidates - the sizes available, in any order.
46
+ * @param limits - the seam's limits; absent falls back to
47
+ * {@link ASSUMED_IMAGE_LIMITS} rather than to knowing nothing.
48
+ * @returns the same candidates, best first.
49
+ */
50
+ export declare function orderBySuitability(candidates: readonly ImageCandidate[], limits: ImageLimits | undefined): ImageCandidate[];
51
+ /**
52
+ * Whether a candidate's dimensions are known to be acceptable.
53
+ *
54
+ * Unknown dimensions are not treated as fitting: a document arrives with none,
55
+ * and claiming it fits would put it ahead of a size that demonstrably does.
56
+ */
57
+ export declare function withinLimits(candidate: ImageCandidate, limits: ImageLimits | undefined): boolean;
58
+ /**
59
+ * Whether a failure means "this image is too big", rather than anything else.
60
+ *
61
+ * Only these are worth retrying at a smaller size; a malformed file or a full
62
+ * disk would fail identically however small the image was.
63
+ *
64
+ * @param error - whatever the attachment seam threw.
65
+ */
66
+ export declare function isTooLarge(error: unknown): boolean;
67
+ /** Say how big an image was and what it may be, for a refusal that helps. */
68
+ export declare function describeSize(candidate: ImageCandidate | undefined, limits: ImageLimits | undefined): string | undefined;
69
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/media/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,iEAAiE;AACjE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAyC,CAAA;AAE5E;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,SAAS,cAAc,EAAE,EACrC,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,cAAc,EAAE,CAOlB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,cAAc,EACzB,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAWT;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQlD;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAC1B,SAAS,EAAE,cAAc,GAAG,SAAS,EACrC,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,MAAM,GAAG,SAAS,CAUpB"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Choosing which size of a photo to store.
3
+ *
4
+ * The harness attachment seam refuses an image whose longest side is over
5
+ * `maxImageDimension` — 2000 pixels by default. Every modern phone screenshot
6
+ * is over it: 1179×2556 on an iPhone, 1080×2400 on most Android. Taking the
7
+ * largest size Telegram offers, which is what "best quality" naively means,
8
+ * therefore fails for the single most common thing anyone sends a bot.
9
+ *
10
+ * Telegram already solves this. A photo arrives as several rendered sizes, and
11
+ * one of the smaller ones is almost always both within the limit and far more
12
+ * than a model needs to read a receipt. So the largest size that fits is the
13
+ * one to send, and the rest are kept as fallbacks: the limit is the harness's
14
+ * to change, so a rejection is answered by stepping down rather than by
15
+ * trusting the number this side read.
16
+ */
17
+ /**
18
+ * What to assume when the seam publishes nothing.
19
+ *
20
+ * The harness's own default, so a store that keeps its limits to itself is
21
+ * still met with a sensible guess rather than with the smallest thumbnail
22
+ * Telegram has — which is 90 pixels wide and legible to nobody. A guess is
23
+ * safe here because it only decides what to try first: a refusal steps down.
24
+ */
25
+ export const ASSUMED_IMAGE_LIMITS = { maxImageDimension: 2000 };
26
+ /**
27
+ * Order candidates by which to try first.
28
+ *
29
+ * Everything known to fit comes first, largest of those leading, because a
30
+ * bigger image is easier to read. Everything else follows in ascending order,
31
+ * so a blind attempt starts with the one most likely to be accepted.
32
+ *
33
+ * @param candidates - the sizes available, in any order.
34
+ * @param limits - the seam's limits; absent falls back to
35
+ * {@link ASSUMED_IMAGE_LIMITS} rather than to knowing nothing.
36
+ * @returns the same candidates, best first.
37
+ */
38
+ export function orderBySuitability(candidates, limits) {
39
+ const applied = limits ?? ASSUMED_IMAGE_LIMITS;
40
+ const byArea = [...candidates].sort((a, b) => area(a) - area(b));
41
+ const fits = byArea.filter((candidate) => withinLimits(candidate, applied));
42
+ const rest = byArea.filter((candidate) => !withinLimits(candidate, applied));
43
+ return [...fits.reverse(), ...rest];
44
+ }
45
+ /**
46
+ * Whether a candidate's dimensions are known to be acceptable.
47
+ *
48
+ * Unknown dimensions are not treated as fitting: a document arrives with none,
49
+ * and claiming it fits would put it ahead of a size that demonstrably does.
50
+ */
51
+ export function withinLimits(candidate, limits) {
52
+ const { width, height } = candidate;
53
+ if (width === undefined || height === undefined)
54
+ return false;
55
+ if (limits === undefined)
56
+ return false;
57
+ if (limits.maxImageDimension !== undefined && Math.max(width, height) > limits.maxImageDimension) {
58
+ return false;
59
+ }
60
+ if (limits.maxImagePixels !== undefined && width * height > limits.maxImagePixels)
61
+ return false;
62
+ return true;
63
+ }
64
+ /**
65
+ * Whether a failure means "this image is too big", rather than anything else.
66
+ *
67
+ * Only these are worth retrying at a smaller size; a malformed file or a full
68
+ * disk would fail identically however small the image was.
69
+ *
70
+ * @param error - whatever the attachment seam threw.
71
+ */
72
+ export function isTooLarge(error) {
73
+ const code = error?.code;
74
+ if (code === 'IMAGE_DIMENSION_TOO_LARGE' || code === 'IMAGE_TOO_MANY_PIXELS')
75
+ return true;
76
+ // The code is the contract, but a seam that reports only a message should
77
+ // still step down rather than give up.
78
+ const message = error instanceof Error ? error.message : String(error ?? '');
79
+ return /pixel limit|too many pixels|dimension/i.test(message);
80
+ }
81
+ /** Say how big an image was and what it may be, for a refusal that helps. */
82
+ export function describeSize(candidate, limits) {
83
+ const dimension = limits?.maxImageDimension;
84
+ if (dimension === undefined)
85
+ return undefined;
86
+ const measured = candidate?.width !== undefined && candidate.height !== undefined
87
+ ? `${candidate.width}×${candidate.height} `
88
+ : '';
89
+ return `That image ${measured}is larger than this harness stores — the limit is ${dimension} pixels per side.`;
90
+ }
91
+ /** Pixels, or zero where Telegram reported no dimensions. */
92
+ function area(candidate) {
93
+ if (candidate.width === undefined || candidate.height === undefined)
94
+ return 0;
95
+ return candidate.width * candidate.height;
96
+ }
97
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/media/limits.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Reading the text in an image without a model.
3
+ *
4
+ * A fallback, and only that. When no vision model is configured the image is
5
+ * otherwise refused before it is even downloaded, and the user gets a sentence
6
+ * about model configuration instead of an answer. Poor OCR beats nothing, and
7
+ * the thing people most often send a coding agent from a phone — a screenshot
8
+ * of an error, a log, a stack trace — is exactly what OCR is good at: crisp
9
+ * text, high contrast, no perspective.
10
+ *
11
+ * What it is NOT is a vision model. It reads text; it does not see. A
12
+ * whiteboard, an architecture diagram, a chart, a UI layout all come back as
13
+ * scattered words with no structure and nothing to say what the picture was.
14
+ * So its output is labelled as OCR wherever it goes — an agent handed
15
+ * unlabelled OCR treats a misread digit as a fact.
16
+ *
17
+ * Tesseract is never assumed. It ships with no operating system this runs on,
18
+ * so its absence is the normal case and is detected rather than discovered
19
+ * halfway through a message.
20
+ */
21
+ import type { Logger } from '../harness/types.js';
22
+ /** Running a command; injected so tests need no Tesseract. */
23
+ export type CommandRunner = (command: string, args: readonly string[], signal: AbortSignal) => Promise<string>;
24
+ /** Construction options. */
25
+ export interface OcrReaderOptions {
26
+ /** Languages to read, as Tesseract names them. Several join with `+`. */
27
+ readonly languages?: string;
28
+ readonly binary?: string;
29
+ readonly run?: CommandRunner;
30
+ readonly timeoutMs?: number;
31
+ readonly logger?: Logger;
32
+ }
33
+ export declare class OcrReader {
34
+ private readonly options;
35
+ private readonly logger;
36
+ /** Cached, because probing spawns a process and the answer cannot change. */
37
+ private probed;
38
+ constructor(options?: OcrReaderOptions);
39
+ /**
40
+ * Whether Tesseract is actually installed here.
41
+ *
42
+ * Probed once and remembered. A deployment without it is the ordinary case,
43
+ * not a fault: nothing this runs on ships Tesseract.
44
+ */
45
+ available(): Promise<boolean>;
46
+ /**
47
+ * Read the text in an image.
48
+ *
49
+ * @param data - the image bytes.
50
+ * @returns the text, or undefined when there was none worth having.
51
+ */
52
+ read(data: Uint8Array): Promise<string | undefined>;
53
+ /** Ask Tesseract whether it is there. */
54
+ private probe;
55
+ /** Run the binary and return what it printed. */
56
+ private exec;
57
+ }
58
+ /**
59
+ * How the text is introduced to the agent.
60
+ *
61
+ * The caveat is not politeness. An agent handed unlabelled OCR treats a
62
+ * misread digit as a fact, and a receipt's amount is exactly the sort of thing
63
+ * it gets wrong.
64
+ */
65
+ export declare function labelOcr(text: string): string;
66
+ //# sourceMappingURL=ocr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ocr.d.ts","sourceRoot":"","sources":["../../src/media/ocr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AASjD,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,MAAM,EAAE,WAAW,KAChB,OAAO,CAAC,MAAM,CAAC,CAAA;AAEpB,4BAA4B;AAC5B,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAA;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,qBAAa,SAAS;IAKR,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,6EAA6E;IAC7E,OAAO,CAAC,MAAM,CAA8B;gBAEf,OAAO,GAAE,gBAAqB;IAI3D;;;;;OAKG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAKnC;;;;;OAKG;IACG,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IA8BzD,yCAAyC;YAC3B,KAAK;IAcnB,iDAAiD;IACjD,OAAO,CAAC,IAAI;CAWb;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO7C"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Reading the text in an image without a model.
3
+ *
4
+ * A fallback, and only that. When no vision model is configured the image is
5
+ * otherwise refused before it is even downloaded, and the user gets a sentence
6
+ * about model configuration instead of an answer. Poor OCR beats nothing, and
7
+ * the thing people most often send a coding agent from a phone — a screenshot
8
+ * of an error, a log, a stack trace — is exactly what OCR is good at: crisp
9
+ * text, high contrast, no perspective.
10
+ *
11
+ * What it is NOT is a vision model. It reads text; it does not see. A
12
+ * whiteboard, an architecture diagram, a chart, a UI layout all come back as
13
+ * scattered words with no structure and nothing to say what the picture was.
14
+ * So its output is labelled as OCR wherever it goes — an agent handed
15
+ * unlabelled OCR treats a misread digit as a fact.
16
+ *
17
+ * Tesseract is never assumed. It ships with no operating system this runs on,
18
+ * so its absence is the normal case and is detected rather than discovered
19
+ * halfway through a message.
20
+ */
21
+ import { execFile } from 'node:child_process';
22
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
23
+ import { tmpdir } from 'node:os';
24
+ import { join } from 'node:path';
25
+ import { SILENT_LOGGER } from '../harness/types.js';
26
+ /** How long one page may take before the message moves on without it. */
27
+ const DEFAULT_TIMEOUT_MS = 30_000;
28
+ /** Below this many characters, whatever came back is noise rather than text. */
29
+ const MIN_USEFUL_CHARS = 8;
30
+ export class OcrReader {
31
+ options;
32
+ logger;
33
+ /** Cached, because probing spawns a process and the answer cannot change. */
34
+ probed;
35
+ constructor(options = {}) {
36
+ this.options = options;
37
+ this.logger = options.logger ?? SILENT_LOGGER;
38
+ }
39
+ /**
40
+ * Whether Tesseract is actually installed here.
41
+ *
42
+ * Probed once and remembered. A deployment without it is the ordinary case,
43
+ * not a fault: nothing this runs on ships Tesseract.
44
+ */
45
+ async available() {
46
+ this.probed ??= this.probe();
47
+ return await this.probed;
48
+ }
49
+ /**
50
+ * Read the text in an image.
51
+ *
52
+ * @param data - the image bytes.
53
+ * @returns the text, or undefined when there was none worth having.
54
+ */
55
+ async read(data) {
56
+ if (!(await this.available()))
57
+ return undefined;
58
+ const directory = await mkdtemp(join(tmpdir(), 'dsh-telegram-ocr-'));
59
+ const input = join(directory, 'image');
60
+ const controller = new AbortController();
61
+ const timer = setTimeout(() => controller.abort(), this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
62
+ try {
63
+ await writeFile(input, data);
64
+ // `stdout` rather than a file: one fewer artifact to clean up, and the
65
+ // text is small enough that a pipe is the simpler path.
66
+ const text = await this.exec([input, 'stdout', '-l', this.options.languages ?? 'eng'], controller.signal);
67
+ const tidy = tidyUp(text);
68
+ return tidy.length >= MIN_USEFUL_CHARS ? tidy : undefined;
69
+ }
70
+ catch (error) {
71
+ this.logger.warn('[dsh-telegram] could not read the image with OCR', error);
72
+ return undefined;
73
+ }
74
+ finally {
75
+ clearTimeout(timer);
76
+ await rm(directory, { recursive: true, force: true }).catch(() => undefined);
77
+ }
78
+ }
79
+ /** Ask Tesseract whether it is there. */
80
+ async probe() {
81
+ const controller = new AbortController();
82
+ const timer = setTimeout(() => controller.abort(), 5000);
83
+ try {
84
+ await this.exec(['--version'], controller.signal);
85
+ return true;
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ finally {
91
+ clearTimeout(timer);
92
+ }
93
+ }
94
+ /** Run the binary and return what it printed. */
95
+ exec(args, signal) {
96
+ const binary = this.options.binary ?? 'tesseract';
97
+ if (this.options.run)
98
+ return this.options.run(binary, args, signal);
99
+ return new Promise((resolve, reject) => {
100
+ execFile(binary, [...args], { signal, maxBuffer: 8 * 1024 * 1024 }, (error, stdout) => {
101
+ if (error)
102
+ reject(error);
103
+ else
104
+ resolve(stdout);
105
+ });
106
+ });
107
+ }
108
+ }
109
+ /**
110
+ * How the text is introduced to the agent.
111
+ *
112
+ * The caveat is not politeness. An agent handed unlabelled OCR treats a
113
+ * misread digit as a fact, and a receipt's amount is exactly the sort of thing
114
+ * it gets wrong.
115
+ */
116
+ export function labelOcr(text) {
117
+ return ('Text read from the image by OCR. No vision model is configured, so ' +
118
+ 'nothing has described the picture itself and the reading may contain ' +
119
+ 'mistakes — treat exact figures with care:\n\n' +
120
+ text);
121
+ }
122
+ /** Collapse the ragged whitespace OCR leaves between columns and lines. */
123
+ function tidyUp(text) {
124
+ return text
125
+ .split('\n')
126
+ .map((line) => line.replace(/[ \t]+/g, ' ').trim())
127
+ .filter((line) => line !== '')
128
+ .join('\n')
129
+ .trim();
130
+ }
131
+ //# sourceMappingURL=ocr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ocr.js","sourceRoot":"","sources":["../../src/media/ocr.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
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 type { Logger } from '../harness/types.js';
21
+ /** Telegram refuses a photo above this; a document goes up to 50 MB. */
22
+ export declare const PHOTO_LIMIT_BYTES: number;
23
+ /** What a capture produced. */
24
+ export type Capture = {
25
+ readonly kind: 'image';
26
+ readonly data: Uint8Array;
27
+ readonly filename: string;
28
+ } | {
29
+ readonly kind: 'unsupported';
30
+ readonly platform: string;
31
+ } | {
32
+ readonly kind: 'failed';
33
+ readonly reason: string;
34
+ };
35
+ /** Running the capture tool; injected so tests need no screen. */
36
+ export type CaptureRunner = (output: string, signal: AbortSignal) => Promise<void>;
37
+ /** Construction options. */
38
+ export interface ScreenshotOptions {
39
+ readonly platform?: string;
40
+ readonly run?: CaptureRunner;
41
+ readonly timeoutMs?: number;
42
+ readonly logger?: Logger;
43
+ }
44
+ export declare class Screenshotter {
45
+ private readonly options;
46
+ private readonly logger;
47
+ constructor(options?: ScreenshotOptions);
48
+ /** Whether this platform has a capture tool this knows how to drive. */
49
+ get available(): boolean;
50
+ /**
51
+ * Capture the screen.
52
+ *
53
+ * @returns the image, or why there is none.
54
+ */
55
+ take(): Promise<Capture>;
56
+ }
57
+ //# sourceMappingURL=screenshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/media/screenshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAOH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAMjD,wEAAwE;AACxE,eAAO,MAAM,iBAAiB,QAAmB,CAAA;AAEjD,+BAA+B;AAC/B,MAAM,MAAM,OAAO,GACf;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAChF;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAExD,kEAAkE;AAClE,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAElF,4BAA4B;AAC5B,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAA;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,qBAAa,aAAa;IAGZ,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;gBAEF,OAAO,GAAE,iBAAsB;IAI5D,wEAAwE;IACxE,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;CA2B/B"}