@llblab/pi-telegram 0.46.0 → 0.48.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 (204) hide show
  1. package/AGENTS.md +4 -2
  2. package/BACKLOG.md +0 -6
  3. package/CHANGELOG.md +14 -0
  4. package/README.md +5 -2
  5. package/dist/api/activity.d.ts +6 -0
  6. package/dist/api/activity.js +6 -0
  7. package/dist/api/commands.d.ts +6 -0
  8. package/dist/api/commands.js +6 -0
  9. package/dist/api/delivery.d.ts +6 -0
  10. package/dist/api/delivery.js +6 -0
  11. package/dist/api/inbound.d.ts +6 -0
  12. package/dist/api/inbound.js +6 -0
  13. package/dist/api/keyboard.d.ts +6 -0
  14. package/dist/api/keyboard.js +6 -0
  15. package/dist/api/outbound.d.ts +6 -0
  16. package/dist/api/outbound.js +6 -0
  17. package/dist/api/sections.d.ts +7 -0
  18. package/dist/api/sections.js +6 -0
  19. package/dist/api/status.d.ts +6 -0
  20. package/dist/api/status.js +6 -0
  21. package/dist/api/updates.d.ts +6 -0
  22. package/dist/api/updates.js +6 -0
  23. package/dist/api/voice.d.ts +6 -0
  24. package/dist/api/voice.js +6 -0
  25. package/dist/index.d.ts +6 -0
  26. package/dist/index.js +6 -0
  27. package/dist/lib/activity-verbosity.d.ts +52 -0
  28. package/dist/lib/activity-verbosity.js +596 -0
  29. package/dist/lib/activity.d.ts +220 -0
  30. package/dist/lib/activity.js +574 -0
  31. package/dist/lib/agent-messages.d.ts +28 -0
  32. package/dist/lib/agent-messages.js +86 -0
  33. package/dist/lib/bindings.d.ts +250 -0
  34. package/dist/lib/bindings.js +936 -0
  35. package/dist/lib/bus-api.d.ts +18 -0
  36. package/dist/lib/bus-api.js +254 -0
  37. package/dist/lib/bus-follower.d.ts +464 -0
  38. package/dist/lib/bus-follower.js +1686 -0
  39. package/dist/lib/bus-leader.d.ts +292 -0
  40. package/dist/lib/bus-leader.js +2242 -0
  41. package/dist/lib/bus-transport.d.ts +64 -0
  42. package/dist/lib/bus-transport.js +140 -0
  43. package/dist/lib/bus.d.ts +518 -0
  44. package/dist/lib/bus.js +2055 -0
  45. package/dist/lib/channel-posts.d.ts +170 -0
  46. package/dist/lib/channel-posts.js +611 -0
  47. package/dist/lib/command-templates.d.ts +70 -0
  48. package/dist/lib/command-templates.js +744 -0
  49. package/dist/lib/commands.d.ts +541 -0
  50. package/dist/lib/commands.js +1155 -0
  51. package/dist/lib/config.d.ts +252 -0
  52. package/dist/lib/config.js +889 -0
  53. package/dist/lib/delivery.d.ts +163 -0
  54. package/dist/lib/delivery.js +542 -0
  55. package/dist/lib/extension.d.ts +7 -0
  56. package/dist/lib/extension.js +1604 -0
  57. package/dist/lib/generative-app-worker.mjs +104 -0
  58. package/dist/lib/generative-apps.d.ts +212 -0
  59. package/dist/lib/generative-apps.js +1006 -0
  60. package/dist/lib/inbound.d.ts +91 -0
  61. package/dist/lib/inbound.js +502 -0
  62. package/dist/lib/journal.d.ts +665 -0
  63. package/dist/lib/journal.js +3701 -0
  64. package/dist/lib/keyboard.d.ts +23 -0
  65. package/dist/lib/keyboard.js +37 -0
  66. package/dist/lib/lifecycle.d.ts +157 -0
  67. package/dist/lib/lifecycle.js +395 -0
  68. package/dist/lib/locks.d.ts +164 -0
  69. package/dist/lib/locks.js +1208 -0
  70. package/dist/lib/logging.d.ts +50 -0
  71. package/dist/lib/logging.js +274 -0
  72. package/dist/lib/media.d.ts +181 -0
  73. package/dist/lib/media.js +602 -0
  74. package/dist/lib/menu-model.d.ts +217 -0
  75. package/dist/lib/menu-model.js +663 -0
  76. package/dist/lib/menu-queue.d.ts +37 -0
  77. package/dist/lib/menu-queue.js +408 -0
  78. package/dist/lib/menu-settings.d.ts +115 -0
  79. package/dist/lib/menu-settings.js +595 -0
  80. package/dist/lib/menu-status.d.ts +32 -0
  81. package/dist/lib/menu-status.js +112 -0
  82. package/dist/lib/menu-thinking.d.ts +29 -0
  83. package/dist/lib/menu-thinking.js +82 -0
  84. package/dist/lib/menu.d.ts +171 -0
  85. package/dist/lib/menu.js +323 -0
  86. package/dist/lib/model.d.ts +127 -0
  87. package/dist/lib/model.js +409 -0
  88. package/dist/lib/outbound-attachments.d.ts +241 -0
  89. package/dist/lib/outbound-attachments.js +639 -0
  90. package/dist/lib/outbound-buttons.d.ts +69 -0
  91. package/dist/lib/outbound-buttons.js +248 -0
  92. package/dist/lib/outbound-markup.d.ts +42 -0
  93. package/dist/lib/outbound-markup.js +678 -0
  94. package/dist/lib/outbound-voice.d.ts +55 -0
  95. package/dist/lib/outbound-voice.js +152 -0
  96. package/dist/lib/outbound.d.ts +185 -0
  97. package/dist/lib/outbound.js +516 -0
  98. package/dist/lib/ownership.d.ts +77 -0
  99. package/dist/lib/ownership.js +174 -0
  100. package/dist/lib/paths.d.ts +40 -0
  101. package/dist/lib/paths.js +94 -0
  102. package/dist/lib/pi.d.ts +72 -0
  103. package/dist/lib/pi.js +121 -0
  104. package/dist/lib/polling.d.ts +351 -0
  105. package/dist/lib/polling.js +1196 -0
  106. package/dist/lib/preview.d.ts +192 -0
  107. package/dist/lib/preview.js +614 -0
  108. package/dist/lib/prompt-templates.d.ts +24 -0
  109. package/dist/lib/prompt-templates.js +118 -0
  110. package/dist/lib/prompts.d.ts +57 -0
  111. package/dist/lib/prompts.js +167 -0
  112. package/dist/lib/queue.d.ts +766 -0
  113. package/dist/lib/queue.js +1965 -0
  114. package/dist/lib/recovery.d.ts +86 -0
  115. package/dist/lib/recovery.js +285 -0
  116. package/dist/lib/rendering.d.ts +20 -0
  117. package/dist/lib/rendering.js +983 -0
  118. package/dist/lib/replies.d.ts +214 -0
  119. package/dist/lib/replies.js +737 -0
  120. package/dist/lib/routing.d.ts +207 -0
  121. package/dist/lib/routing.js +2282 -0
  122. package/dist/lib/runtime.d.ts +172 -0
  123. package/dist/lib/runtime.js +402 -0
  124. package/dist/lib/sections.d.ts +165 -0
  125. package/dist/lib/sections.js +327 -0
  126. package/dist/lib/setup.d.ts +82 -0
  127. package/dist/lib/setup.js +150 -0
  128. package/dist/lib/skills.d.ts +8 -0
  129. package/dist/lib/skills.js +12 -0
  130. package/dist/lib/status.d.ts +442 -0
  131. package/dist/lib/status.js +1008 -0
  132. package/dist/lib/sync.d.ts +179 -0
  133. package/dist/lib/sync.js +782 -0
  134. package/dist/lib/target.d.ts +20 -0
  135. package/dist/lib/target.js +27 -0
  136. package/dist/lib/telegram-api.d.ts +541 -0
  137. package/dist/lib/telegram-api.js +1159 -0
  138. package/dist/lib/text-groups.d.ts +89 -0
  139. package/dist/lib/text-groups.js +317 -0
  140. package/dist/lib/thread-cleanup-manager.d.ts +288 -0
  141. package/dist/lib/thread-cleanup-manager.js +560 -0
  142. package/dist/lib/thread-display.d.ts +46 -0
  143. package/dist/lib/thread-display.js +257 -0
  144. package/dist/lib/thread-naming.d.ts +46 -0
  145. package/dist/lib/thread-naming.js +78 -0
  146. package/dist/lib/thread-reconciler.d.ts +239 -0
  147. package/dist/lib/thread-reconciler.js +644 -0
  148. package/dist/lib/threads.d.ts +621 -0
  149. package/dist/lib/threads.js +3679 -0
  150. package/dist/lib/time-injection.d.ts +15 -0
  151. package/dist/lib/time-injection.js +56 -0
  152. package/dist/lib/turns.d.ts +109 -0
  153. package/dist/lib/turns.js +500 -0
  154. package/dist/lib/updates.d.ts +1290 -0
  155. package/dist/lib/updates.js +3634 -0
  156. package/dist/lib/voice.d.ts +117 -0
  157. package/dist/lib/voice.js +174 -0
  158. package/dist/lib/workspace-admission.d.ts +264 -0
  159. package/dist/lib/workspace-admission.js +1136 -0
  160. package/dist/lib/workspace-retirement.d.ts +219 -0
  161. package/dist/lib/workspace-retirement.js +587 -0
  162. package/dist/lib/workspace-slots.d.ts +30 -0
  163. package/dist/lib/workspace-slots.js +54 -0
  164. package/dist/package.json +126 -0
  165. package/dist/pi-telegram/index.js +1 -0
  166. package/dist/skills/generated-control-surface/SKILL.md +107 -0
  167. package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
  168. package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
  169. package/dist/skills/generative-apps/SKILL.md +115 -0
  170. package/dist/skills/show-me/SKILL.md +166 -0
  171. package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
  172. package/dist/skills/telegram-bridge/SKILL.md +129 -0
  173. package/dist/skills/telegram-bridge/references/configuration.md +15 -0
  174. package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
  175. package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
  176. package/docs/README.md +2 -0
  177. package/docs/architecture.md +1 -1
  178. package/docs/compact-matrix-literal.md +10 -3
  179. package/docs/generative-apps.md +15 -13
  180. package/docs/multi-instance-bus.md +6 -4
  181. package/docs/outbound.md +1 -1
  182. package/docs/public-api.md +3 -3
  183. package/lib/bindings.ts +89 -6
  184. package/lib/bus-follower.ts +7 -3
  185. package/lib/bus-leader.ts +44 -19
  186. package/lib/bus.ts +4 -1
  187. package/lib/commands.ts +2 -2
  188. package/lib/config.ts +14 -7
  189. package/lib/delivery.ts +38 -6
  190. package/lib/extension.ts +7 -0
  191. package/lib/generative-apps.ts +379 -13
  192. package/lib/menu-settings.ts +20 -6
  193. package/lib/outbound-markup.ts +16 -7
  194. package/lib/status.ts +15 -12
  195. package/lib/telegram-api.ts +12 -1
  196. package/lib/thread-display.ts +96 -18
  197. package/package.json +56 -13
  198. package/scripts/build-dist.mjs +44 -0
  199. package/scripts/measure-bus.mjs +8 -1
  200. package/scripts/measure-workspace.mjs +8 -1
  201. package/skills/generative-apps/SKILL.md +1 -1
  202. package/skills/show-me/SKILL.md +1 -1
  203. package/skills/show-me/references/telegram-surfaces.md +1 -1
  204. package/skills/telegram-bridge/SKILL.md +1 -1
@@ -0,0 +1,596 @@
1
+ /**
2
+ * Bridge-owned Telegram activity verbosity projection
3
+ * Zones: telegram activity, rich rendering, operational delivery
4
+ * Owns persistent bounded thinking and tool disclosures; excludes activity normalization, assistant answer rendering, and transport authority policy
5
+ */
6
+ import { escapeHtml, renderTelegramInlineMarkdownHtml, } from "./rendering.js";
7
+ export const TELEGRAM_ACTIVITY_DETAIL_MAX_CHARS = 1_200;
8
+ export const TELEGRAM_ACTIVITY_MESSAGE_MAX_CHARS = 3_900;
9
+ export const TELEGRAM_ACTIVITY_MESSAGE_MAX_TOOLS = 6;
10
+ export const TELEGRAM_REASONING_MESSAGE_MAX_FRAMES = 24;
11
+ export const TELEGRAM_REASONING_BUFFER_MAX_CHARS = 1_200;
12
+ export const TELEGRAM_REASONING_MIN_INTERVAL_MS = 1_200;
13
+ export const TELEGRAM_TOOL_UPDATE_MAX_ENTRIES = 4;
14
+ function targetEquals(left, right) {
15
+ return left.chatId === right.chatId && left.threadId === right.threadId;
16
+ }
17
+ function redactActivityText(text) {
18
+ return text
19
+ .replace(/\b\d{8,12}:[A-Za-z0-9_-]{30,}\b/g, "[REDACTED_BOT_TOKEN]")
20
+ .replace(/\b(Bearer\s+)[A-Za-z0-9._~+/=-]{16,}\b/gi, "$1[REDACTED]")
21
+ .replace(/(["']?(?:api[_-]?key|token|password|secret)["']?\s*[:=]\s*["']?)[^"',\s}]+/gi, "$1[REDACTED]");
22
+ }
23
+ function formatActivityJson(value, depth = 0) {
24
+ const indent = " ".repeat(depth);
25
+ if (Array.isArray(value)) {
26
+ if (value.length === 0)
27
+ return [`${indent}[]`];
28
+ if (value.every((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry))) {
29
+ const lines = [`${indent}[{`];
30
+ value.forEach((entry, index) => {
31
+ const fields = Object.entries(entry);
32
+ fields.forEach(([key, nested], fieldIndex) => {
33
+ const nestedLines = formatActivityJson(nested, depth + 1);
34
+ const nestedIndent = " ".repeat(depth + 1);
35
+ lines.push(`${nestedIndent}${JSON.stringify(key)}: ${nestedLines[0].slice(nestedIndent.length)}`, ...nestedLines.slice(1));
36
+ if (fieldIndex < fields.length - 1) {
37
+ lines[lines.length - 1] += ",";
38
+ }
39
+ });
40
+ lines.push(index < value.length - 1 ? `${indent}}, {` : `${indent}}]`);
41
+ });
42
+ return lines;
43
+ }
44
+ const lines = [`${indent}[`];
45
+ value.forEach((entry, index) => {
46
+ const nestedLines = formatActivityJson(entry, depth + 1);
47
+ if (index < value.length - 1) {
48
+ nestedLines[nestedLines.length - 1] += ",";
49
+ }
50
+ lines.push(...nestedLines);
51
+ });
52
+ lines.push(`${indent}]`);
53
+ return lines;
54
+ }
55
+ if (value !== null && typeof value === "object") {
56
+ const entries = Object.entries(value);
57
+ if (entries.length === 0)
58
+ return [`${indent}{}`];
59
+ const lines = [`${indent}{`];
60
+ entries.forEach(([key, nested], index) => {
61
+ const nestedLines = formatActivityJson(nested, depth + 1);
62
+ const nestedIndent = " ".repeat(depth + 1);
63
+ lines.push(`${nestedIndent}${JSON.stringify(key)}: ${nestedLines[0].slice(nestedIndent.length)}`, ...nestedLines.slice(1));
64
+ if (index < entries.length - 1)
65
+ lines[lines.length - 1] += ",";
66
+ });
67
+ lines.push(`${indent}}`);
68
+ return lines;
69
+ }
70
+ return [`${indent}${JSON.stringify(value)}`];
71
+ }
72
+ function serializeActivityValue(value) {
73
+ const seen = new WeakSet();
74
+ let text;
75
+ try {
76
+ const normalized = JSON.stringify(value, (_key, nested) => {
77
+ if (typeof nested === "bigint")
78
+ return nested.toString();
79
+ if (nested && typeof nested === "object") {
80
+ if (seen.has(nested))
81
+ return "[Circular]";
82
+ seen.add(nested);
83
+ }
84
+ return nested;
85
+ }) ?? JSON.stringify(String(value));
86
+ text = formatActivityJson(JSON.parse(normalized)).join("\n");
87
+ }
88
+ catch {
89
+ text = JSON.stringify(String(value));
90
+ }
91
+ const redacted = redactActivityText(text);
92
+ if (redacted.length <= TELEGRAM_ACTIVITY_DETAIL_MAX_CHARS)
93
+ return redacted;
94
+ const omitted = redacted.length - TELEGRAM_ACTIVITY_DETAIL_MAX_CHARS;
95
+ return `${redacted.slice(0, TELEGRAM_ACTIVITY_DETAIL_MAX_CHARS)}\n… [${omitted} chars truncated]`;
96
+ }
97
+ function neutralizeActivityAutoLinks(text) {
98
+ return text.replace(/\b(https?:\/\/)(?=\S)/gi, "$1\u200b");
99
+ }
100
+ function escapeActivityEvidenceHtml(text) {
101
+ return escapeHtml(neutralizeActivityAutoLinks(text));
102
+ }
103
+ function renderThinkingActivityEvidenceHtml(text) {
104
+ return renderTelegramInlineMarkdownHtml(neutralizeActivityAutoLinks(text), {
105
+ allowLinks: false,
106
+ });
107
+ }
108
+ function formatToolActivityLabel(label) {
109
+ return label
110
+ .split("_")
111
+ .filter(Boolean)
112
+ .map((word) => {
113
+ const repeatedPrefix = word.match(/^([a-z])\1*/iu)?.[0] ?? "";
114
+ if (repeatedPrefix.length === 2 || repeatedPrefix.length === 3) {
115
+ return `${repeatedPrefix.toUpperCase()}${word.slice(repeatedPrefix.length)}`;
116
+ }
117
+ return `${word[0].toUpperCase()}${word.slice(1)}`;
118
+ })
119
+ .join(" ");
120
+ }
121
+ function renderToolActivityHtml(tool) {
122
+ const evidence = [`"arguments": ${tool.args}`];
123
+ if (tool.droppedUpdates > 0) {
124
+ evidence.push(`… [${tool.droppedUpdates} earlier updates omitted]`);
125
+ }
126
+ tool.updates.forEach((update, index) => {
127
+ evidence.push(`"update ${tool.droppedUpdates + index + 1}": ${update}`);
128
+ });
129
+ if (tool.complete && tool.result !== undefined) {
130
+ evidence.push(`"${tool.isError ? "error" : "result"}": ${tool.result}`);
131
+ }
132
+ const status = tool.complete
133
+ ? tool.isError
134
+ ? "failed"
135
+ : "done"
136
+ : "running";
137
+ return [
138
+ `<b>${escapeHtml(formatToolActivityLabel(tool.name))}:</b> <code>${status}</code>`,
139
+ `<blockquote expandable>${escapeActivityEvidenceHtml(evidence.join("\n\n"))}</blockquote>`,
140
+ ].join("\n");
141
+ }
142
+ export function renderTelegramToolActivityHtml(tools) {
143
+ return tools.map(renderToolActivityHtml).join("\n\n");
144
+ }
145
+ function createToolActivityDetail(summary, text, isOpen = false) {
146
+ return {
147
+ type: "details",
148
+ summary: { type: "code", text: summary },
149
+ blocks: [{ type: "pre", text, language: "json" }],
150
+ ...(isOpen ? { is_open: true } : {}),
151
+ };
152
+ }
153
+ function renderToolActivityRichBlocks(tool) {
154
+ const status = tool.complete
155
+ ? tool.isError
156
+ ? "failed"
157
+ : "done"
158
+ : "running";
159
+ const evidenceBlocks = [
160
+ createToolActivityDetail("arguments", tool.args, true),
161
+ ];
162
+ tool.updates.forEach((update, index) => {
163
+ const number = tool.droppedUpdates + index + 1;
164
+ const omitted = index === 0 && tool.droppedUpdates > 0
165
+ ? ` (${tool.droppedUpdates} earlier omitted)`
166
+ : "";
167
+ evidenceBlocks.push(createToolActivityDetail(`update ${number}${omitted}`, update));
168
+ });
169
+ if (tool.complete && tool.result !== undefined) {
170
+ evidenceBlocks.push(createToolActivityDetail(tool.isError ? "error" : "result", tool.result));
171
+ }
172
+ return [
173
+ {
174
+ type: "details",
175
+ summary: [
176
+ {
177
+ type: "bold",
178
+ text: `${formatToolActivityLabel(tool.name)}:`,
179
+ },
180
+ " ",
181
+ { type: "code", text: status },
182
+ ],
183
+ blocks: evidenceBlocks,
184
+ },
185
+ ];
186
+ }
187
+ export function renderTelegramToolActivityRichMessage(tools) {
188
+ return {
189
+ blocks: tools.flatMap(renderToolActivityRichBlocks),
190
+ skip_entity_detection: true,
191
+ };
192
+ }
193
+ function toolMessageSize(tools) {
194
+ return renderTelegramToolActivityHtml(tools).length;
195
+ }
196
+ function isKnownSafeRichActivityRejection(error) {
197
+ return error instanceof Error && /HTTP 400: Bad Request:/i.test(error.message);
198
+ }
199
+ export function renderTelegramThinkingActivityHtml(text) {
200
+ return `<blockquote expandable>${renderThinkingActivityEvidenceHtml(text)}</blockquote>`;
201
+ }
202
+ export function createTelegramActivityVerbosityBinding() {
203
+ let runtime;
204
+ return {
205
+ bind(next) {
206
+ runtime = next;
207
+ },
208
+ accept(event) {
209
+ runtime?.accept(event);
210
+ },
211
+ reset() {
212
+ runtime?.reset();
213
+ },
214
+ stop() {
215
+ runtime?.stop();
216
+ },
217
+ waitForIdle() {
218
+ return runtime?.waitForIdle() ?? Promise.resolve();
219
+ },
220
+ };
221
+ }
222
+ export function createTelegramActivityVerbosityRuntime(deps) {
223
+ let active = true;
224
+ let generation = 0;
225
+ let tail = Promise.resolve();
226
+ const getNowMs = deps.getNowMs ?? Date.now;
227
+ let activityId;
228
+ let authority;
229
+ let target;
230
+ let reasoningBuffer = "";
231
+ let reasoningChars = 0;
232
+ let reasoningMessageFrames = 0;
233
+ let lastReasoningMessageChars = 0;
234
+ let reasoningMessage;
235
+ let reasoningBlocked = false;
236
+ let lastReasoningPublishMs = 0;
237
+ let toolMessage;
238
+ const tools = new Map();
239
+ const toolOrder = [];
240
+ const clearActivity = () => {
241
+ activityId = undefined;
242
+ authority = undefined;
243
+ target = undefined;
244
+ reasoningBuffer = "";
245
+ reasoningChars = 0;
246
+ reasoningMessageFrames = 0;
247
+ lastReasoningMessageChars = 0;
248
+ reasoningMessage = undefined;
249
+ reasoningBlocked = false;
250
+ lastReasoningPublishMs = 0;
251
+ toolMessage = undefined;
252
+ tools.clear();
253
+ toolOrder.length = 0;
254
+ };
255
+ const hasAuthority = () => authority !== undefined && deps.isAuthorityActive(authority);
256
+ const isCurrent = (acceptedGeneration, admittedAuthority) => active && generation === acceptedGeneration && admittedAuthority !== undefined && deps.isAuthorityActive(admittedAuthority);
257
+ const ensureActivity = (event, admittedTarget, admittedAuthority) => {
258
+ if (deps.getActivityMode() === "quiet")
259
+ return false;
260
+ if (activityId === event.activityId)
261
+ return hasAuthority();
262
+ clearActivity();
263
+ if (!admittedTarget)
264
+ return false;
265
+ activityId = event.activityId;
266
+ target = admittedTarget;
267
+ authority = admittedAuthority;
268
+ return hasAuthority();
269
+ };
270
+ const closeToolBatch = () => {
271
+ toolMessage = undefined;
272
+ };
273
+ const publishReasoning = async (event, acceptedGeneration) => {
274
+ const admittedAuthority = authority;
275
+ if (!isCurrent(acceptedGeneration, admittedAuthority) ||
276
+ !target ||
277
+ reasoningBlocked) {
278
+ return;
279
+ }
280
+ let retained = reasoningBuffer;
281
+ let body = "";
282
+ do {
283
+ const omitted = reasoningChars - retained.length;
284
+ const text = redactActivityText(omitted > 0
285
+ ? `… [${omitted} earlier chars omitted]\n${retained}`
286
+ : retained);
287
+ body = renderTelegramThinkingActivityHtml(text);
288
+ if (body.length <= TELEGRAM_ACTIVITY_MESSAGE_MAX_CHARS)
289
+ break;
290
+ retained = retained.slice(-Math.max(1, Math.floor(retained.length * 0.75)));
291
+ } while (retained.length > 1);
292
+ const canEdit = reasoningMessage && targetEquals(reasoningMessage.target, target);
293
+ try {
294
+ if (canEdit && reasoningMessage) {
295
+ await deps.editMessageText({
296
+ chat_id: target.chatId,
297
+ message_id: reasoningMessage.messageId,
298
+ text: body,
299
+ parse_mode: "HTML",
300
+ link_preview_options: { is_disabled: true },
301
+ });
302
+ }
303
+ else {
304
+ const sent = await deps.sendMessage({
305
+ chat_id: target.chatId,
306
+ ...(target.threadId === undefined
307
+ ? {}
308
+ : { message_thread_id: target.threadId }),
309
+ text: body,
310
+ parse_mode: "HTML",
311
+ link_preview_options: { is_disabled: true },
312
+ });
313
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
314
+ return;
315
+ reasoningMessage = {
316
+ messageId: sent.message_id,
317
+ target: { ...target },
318
+ };
319
+ }
320
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
321
+ return;
322
+ reasoningMessageFrames += 1;
323
+ lastReasoningMessageChars = reasoningChars;
324
+ lastReasoningPublishMs = getNowMs();
325
+ }
326
+ catch (error) {
327
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
328
+ return;
329
+ reasoningBlocked = true;
330
+ deps.recordFailure?.(canEdit ? "reasoning-edit" : "reasoning-send", event, error);
331
+ }
332
+ };
333
+ const publishTool = async (event, tool, acceptedGeneration) => {
334
+ const admittedAuthority = authority;
335
+ if (!isCurrent(acceptedGeneration, admittedAuthority) || !target) {
336
+ return;
337
+ }
338
+ const canAppend = toolMessage &&
339
+ targetEquals(toolMessage.target, target) &&
340
+ toolMessage.tools.length < TELEGRAM_ACTIVITY_MESSAGE_MAX_TOOLS &&
341
+ toolMessageSize([...toolMessage.tools, tool]) <=
342
+ TELEGRAM_ACTIVITY_MESSAGE_MAX_CHARS;
343
+ try {
344
+ if (canAppend && toolMessage) {
345
+ const nextTools = [...toolMessage.tools, tool];
346
+ try {
347
+ await deps.editMessageText({
348
+ chat_id: target.chatId,
349
+ message_id: toolMessage.messageId,
350
+ ...(toolMessage.format === "rich"
351
+ ? {
352
+ rich_message: renderTelegramToolActivityRichMessage(nextTools),
353
+ }
354
+ : {
355
+ text: renderTelegramToolActivityHtml(nextTools),
356
+ parse_mode: "HTML",
357
+ link_preview_options: { is_disabled: true },
358
+ }),
359
+ });
360
+ }
361
+ catch (error) {
362
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
363
+ return;
364
+ if (toolMessage.format !== "rich" ||
365
+ !isKnownSafeRichActivityRejection(error)) {
366
+ throw error;
367
+ }
368
+ await deps.editMessageText({
369
+ chat_id: target.chatId,
370
+ message_id: toolMessage.messageId,
371
+ text: renderTelegramToolActivityHtml(nextTools),
372
+ parse_mode: "HTML",
373
+ link_preview_options: { is_disabled: true },
374
+ });
375
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
376
+ return;
377
+ toolMessage.format = "html";
378
+ }
379
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
380
+ return;
381
+ toolMessage.tools = nextTools;
382
+ return;
383
+ }
384
+ const body = {
385
+ chat_id: target.chatId,
386
+ ...(target.threadId === undefined
387
+ ? {}
388
+ : { message_thread_id: target.threadId }),
389
+ };
390
+ let sent;
391
+ let format = "rich";
392
+ try {
393
+ sent = await deps.sendRichMessage({
394
+ ...body,
395
+ rich_message: renderTelegramToolActivityRichMessage([tool]),
396
+ });
397
+ }
398
+ catch (error) {
399
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
400
+ return;
401
+ if (!isKnownSafeRichActivityRejection(error))
402
+ throw error;
403
+ sent = await deps.sendMessage({
404
+ ...body,
405
+ text: renderTelegramToolActivityHtml([tool]),
406
+ parse_mode: "HTML",
407
+ link_preview_options: { is_disabled: true },
408
+ });
409
+ format = "html";
410
+ }
411
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
412
+ return;
413
+ toolMessage = {
414
+ messageId: sent.message_id,
415
+ tools: [tool],
416
+ target: { ...target },
417
+ format,
418
+ };
419
+ }
420
+ catch (error) {
421
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
422
+ return;
423
+ closeToolBatch();
424
+ deps.recordFailure?.(canAppend ? "tool-edit" : "tool-send", event, error);
425
+ }
426
+ };
427
+ const process = async (event, acceptedGeneration, admittedTarget, admittedAuthority) => {
428
+ if (event.type === "agent-start" && deps.refreshActivityMode) {
429
+ try {
430
+ await deps.refreshActivityMode();
431
+ }
432
+ catch (error) {
433
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
434
+ return;
435
+ clearActivity();
436
+ activityId = event.activityId;
437
+ deps.recordFailure?.("config-refresh", event, error);
438
+ return;
439
+ }
440
+ }
441
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
442
+ return;
443
+ if (!ensureActivity(event, admittedTarget, admittedAuthority)) {
444
+ if (activityId === event.activityId &&
445
+ deps.getActivityMode() === "quiet") {
446
+ clearActivity();
447
+ }
448
+ return;
449
+ }
450
+ const mode = deps.getActivityMode();
451
+ const showThinking = mode === "thinking" || mode === "verbose";
452
+ const showTools = mode === "tools" || mode === "verbose";
453
+ if (event.type === "assistant-text-delta" ||
454
+ event.type === "assistant-segment" ||
455
+ event.type === "reasoning-delta" ||
456
+ event.type === "reasoning-end") {
457
+ closeToolBatch();
458
+ }
459
+ if (event.type === "reasoning-delta") {
460
+ if (!showThinking)
461
+ return;
462
+ reasoningChars += event.delta.length;
463
+ reasoningBuffer = `${reasoningBuffer}${event.delta}`.slice(-TELEGRAM_REASONING_BUFFER_MAX_CHARS);
464
+ if (reasoningMessageFrames < TELEGRAM_REASONING_MESSAGE_MAX_FRAMES &&
465
+ (reasoningMessageFrames === 0 ||
466
+ (getNowMs() - lastReasoningPublishMs >=
467
+ TELEGRAM_REASONING_MIN_INTERVAL_MS &&
468
+ reasoningChars - lastReasoningMessageChars >= 160))) {
469
+ await publishReasoning(event, acceptedGeneration);
470
+ }
471
+ return;
472
+ }
473
+ if (event.type === "reasoning-end") {
474
+ if (!showThinking)
475
+ return;
476
+ if (reasoningChars === 0 && event.text) {
477
+ reasoningChars = event.text.length;
478
+ reasoningBuffer = event.text.slice(-TELEGRAM_REASONING_BUFFER_MAX_CHARS);
479
+ }
480
+ if (reasoningChars > 0 &&
481
+ reasoningChars > lastReasoningMessageChars &&
482
+ !reasoningBlocked) {
483
+ await publishReasoning(event, acceptedGeneration);
484
+ }
485
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
486
+ return;
487
+ reasoningBuffer = "";
488
+ reasoningChars = 0;
489
+ reasoningMessageFrames = 0;
490
+ lastReasoningMessageChars = 0;
491
+ lastReasoningPublishMs = 0;
492
+ reasoningMessage = undefined;
493
+ reasoningBlocked = false;
494
+ return;
495
+ }
496
+ if (event.type === "tool-start") {
497
+ if (!showTools)
498
+ return;
499
+ tools.set(event.toolCallId, {
500
+ id: event.toolCallId,
501
+ name: event.toolName,
502
+ args: serializeActivityValue(event.args),
503
+ updates: [],
504
+ droppedUpdates: 0,
505
+ complete: false,
506
+ });
507
+ toolOrder.push(event.toolCallId);
508
+ return;
509
+ }
510
+ if (event.type === "tool-update") {
511
+ if (!showTools)
512
+ return;
513
+ const tool = tools.get(event.toolCallId);
514
+ if (!tool)
515
+ return;
516
+ tool.updates.push(serializeActivityValue(event.update));
517
+ if (tool.updates.length > TELEGRAM_TOOL_UPDATE_MAX_ENTRIES) {
518
+ tool.updates.shift();
519
+ tool.droppedUpdates += 1;
520
+ }
521
+ return;
522
+ }
523
+ if (event.type === "tool-end") {
524
+ if (!showTools)
525
+ return;
526
+ const tool = tools.get(event.toolCallId) ?? {
527
+ id: event.toolCallId,
528
+ name: event.toolName,
529
+ args: serializeActivityValue(undefined),
530
+ updates: [],
531
+ droppedUpdates: 0,
532
+ complete: false,
533
+ };
534
+ if (!tools.has(event.toolCallId))
535
+ toolOrder.push(event.toolCallId);
536
+ tool.result = serializeActivityValue(event.result);
537
+ tool.isError = event.isError;
538
+ tool.complete = true;
539
+ tools.set(event.toolCallId, tool);
540
+ while (toolOrder.length > 0) {
541
+ const next = tools.get(toolOrder[0]);
542
+ if (!next?.complete)
543
+ break;
544
+ toolOrder.shift();
545
+ tools.delete(next.id);
546
+ await publishTool(event, next, acceptedGeneration);
547
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
548
+ return;
549
+ }
550
+ return;
551
+ }
552
+ if (event.type === "agent-end" || event.type === "agent-settled") {
553
+ if (reasoningMessage &&
554
+ reasoningChars > lastReasoningMessageChars &&
555
+ !reasoningBlocked) {
556
+ await publishReasoning(event, acceptedGeneration);
557
+ }
558
+ if (!isCurrent(acceptedGeneration, admittedAuthority))
559
+ return;
560
+ clearActivity();
561
+ }
562
+ };
563
+ return {
564
+ accept(event) {
565
+ if (!active)
566
+ return;
567
+ const acceptedGeneration = generation;
568
+ const resolvedTarget = deps.resolveTarget(event);
569
+ const admittedTarget = resolvedTarget ? { ...resolvedTarget } : undefined;
570
+ const admittedAuthority = deps.captureAuthority();
571
+ const enqueue = deps.enqueue ?? ((task) => tail.then(task));
572
+ tail = enqueue(async () => {
573
+ if (!active || generation !== acceptedGeneration || !deps.isAuthorityActive(admittedAuthority))
574
+ return;
575
+ await process(event, acceptedGeneration, admittedTarget, admittedAuthority);
576
+ })
577
+ .catch((error) => {
578
+ deps.recordFailure?.("tool-send", event, error);
579
+ });
580
+ },
581
+ reset() {
582
+ generation += 1;
583
+ clearActivity();
584
+ tail = Promise.resolve();
585
+ },
586
+ stop() {
587
+ active = false;
588
+ generation += 1;
589
+ clearActivity();
590
+ tail = Promise.resolve();
591
+ },
592
+ waitForIdle() {
593
+ return tail;
594
+ },
595
+ };
596
+ }