@llblab/pi-telegram 0.45.11 → 0.47.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/BACKLOG.md +5 -1
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +6 -6
  4. package/dist/api/activity.d.ts +6 -0
  5. package/dist/api/activity.js +6 -0
  6. package/dist/api/commands.d.ts +6 -0
  7. package/dist/api/commands.js +6 -0
  8. package/dist/api/delivery.d.ts +6 -0
  9. package/dist/api/delivery.js +6 -0
  10. package/dist/api/inbound.d.ts +6 -0
  11. package/dist/api/inbound.js +6 -0
  12. package/dist/api/keyboard.d.ts +6 -0
  13. package/dist/api/keyboard.js +6 -0
  14. package/dist/api/outbound.d.ts +6 -0
  15. package/dist/api/outbound.js +6 -0
  16. package/dist/api/sections.d.ts +7 -0
  17. package/dist/api/sections.js +6 -0
  18. package/dist/api/status.d.ts +6 -0
  19. package/dist/api/status.js +6 -0
  20. package/dist/api/updates.d.ts +6 -0
  21. package/dist/api/updates.js +6 -0
  22. package/dist/api/voice.d.ts +6 -0
  23. package/dist/api/voice.js +6 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.js +6 -0
  26. package/dist/lib/activity-verbosity.d.ts +52 -0
  27. package/dist/lib/activity-verbosity.js +596 -0
  28. package/dist/lib/activity.d.ts +220 -0
  29. package/dist/lib/activity.js +574 -0
  30. package/dist/lib/agent-messages.d.ts +28 -0
  31. package/dist/lib/agent-messages.js +86 -0
  32. package/dist/lib/bindings.d.ts +250 -0
  33. package/dist/lib/bindings.js +936 -0
  34. package/dist/lib/bus-api.d.ts +18 -0
  35. package/dist/lib/bus-api.js +254 -0
  36. package/dist/lib/bus-follower.d.ts +464 -0
  37. package/dist/lib/bus-follower.js +1686 -0
  38. package/dist/lib/bus-leader.d.ts +292 -0
  39. package/dist/lib/bus-leader.js +2242 -0
  40. package/dist/lib/bus-transport.d.ts +64 -0
  41. package/dist/lib/bus-transport.js +140 -0
  42. package/dist/lib/bus.d.ts +518 -0
  43. package/dist/lib/bus.js +2055 -0
  44. package/dist/lib/channel-posts.d.ts +170 -0
  45. package/dist/lib/channel-posts.js +611 -0
  46. package/dist/lib/command-templates.d.ts +70 -0
  47. package/dist/lib/command-templates.js +744 -0
  48. package/dist/lib/commands.d.ts +541 -0
  49. package/dist/lib/commands.js +1155 -0
  50. package/dist/lib/config.d.ts +252 -0
  51. package/dist/lib/config.js +889 -0
  52. package/dist/lib/delivery.d.ts +163 -0
  53. package/dist/lib/delivery.js +542 -0
  54. package/dist/lib/extension.d.ts +7 -0
  55. package/dist/lib/extension.js +1608 -0
  56. package/dist/lib/generative-app-worker.mjs +104 -0
  57. package/dist/lib/generative-apps.d.ts +212 -0
  58. package/dist/lib/generative-apps.js +1006 -0
  59. package/dist/lib/inbound.d.ts +91 -0
  60. package/dist/lib/inbound.js +502 -0
  61. package/dist/lib/journal.d.ts +665 -0
  62. package/dist/lib/journal.js +3701 -0
  63. package/dist/lib/keyboard.d.ts +23 -0
  64. package/dist/lib/keyboard.js +37 -0
  65. package/dist/lib/lifecycle.d.ts +157 -0
  66. package/dist/lib/lifecycle.js +395 -0
  67. package/dist/lib/locks.d.ts +164 -0
  68. package/dist/lib/locks.js +1208 -0
  69. package/dist/lib/logging.d.ts +50 -0
  70. package/dist/lib/logging.js +274 -0
  71. package/dist/lib/media.d.ts +181 -0
  72. package/dist/lib/media.js +602 -0
  73. package/dist/lib/menu-model.d.ts +217 -0
  74. package/dist/lib/menu-model.js +663 -0
  75. package/dist/lib/menu-queue.d.ts +37 -0
  76. package/dist/lib/menu-queue.js +408 -0
  77. package/dist/lib/menu-settings.d.ts +116 -0
  78. package/dist/lib/menu-settings.js +595 -0
  79. package/dist/lib/menu-status.d.ts +32 -0
  80. package/dist/lib/menu-status.js +112 -0
  81. package/dist/lib/menu-thinking.d.ts +29 -0
  82. package/dist/lib/menu-thinking.js +82 -0
  83. package/dist/lib/menu.d.ts +171 -0
  84. package/dist/lib/menu.js +323 -0
  85. package/dist/lib/model.d.ts +127 -0
  86. package/dist/lib/model.js +409 -0
  87. package/dist/lib/outbound-attachments.d.ts +241 -0
  88. package/dist/lib/outbound-attachments.js +639 -0
  89. package/dist/lib/outbound-buttons.d.ts +69 -0
  90. package/dist/lib/outbound-buttons.js +248 -0
  91. package/dist/lib/outbound-markup.d.ts +42 -0
  92. package/dist/lib/outbound-markup.js +678 -0
  93. package/dist/lib/outbound-voice.d.ts +55 -0
  94. package/dist/lib/outbound-voice.js +152 -0
  95. package/dist/lib/outbound.d.ts +185 -0
  96. package/dist/lib/outbound.js +516 -0
  97. package/dist/lib/ownership.d.ts +77 -0
  98. package/dist/lib/ownership.js +174 -0
  99. package/dist/lib/paths.d.ts +40 -0
  100. package/dist/lib/paths.js +94 -0
  101. package/dist/lib/pi.d.ts +72 -0
  102. package/dist/lib/pi.js +121 -0
  103. package/dist/lib/polling.d.ts +351 -0
  104. package/dist/lib/polling.js +1196 -0
  105. package/dist/lib/preview.d.ts +192 -0
  106. package/dist/lib/preview.js +614 -0
  107. package/dist/lib/prompt-templates.d.ts +24 -0
  108. package/dist/lib/prompt-templates.js +118 -0
  109. package/dist/lib/prompts.d.ts +57 -0
  110. package/dist/lib/prompts.js +167 -0
  111. package/dist/lib/queue.d.ts +766 -0
  112. package/dist/lib/queue.js +1965 -0
  113. package/dist/lib/recovery.d.ts +86 -0
  114. package/dist/lib/recovery.js +285 -0
  115. package/dist/lib/rendering.d.ts +20 -0
  116. package/dist/lib/rendering.js +983 -0
  117. package/dist/lib/replies.d.ts +214 -0
  118. package/dist/lib/replies.js +737 -0
  119. package/dist/lib/routing.d.ts +207 -0
  120. package/dist/lib/routing.js +2282 -0
  121. package/dist/lib/runtime.d.ts +172 -0
  122. package/dist/lib/runtime.js +402 -0
  123. package/dist/lib/sections.d.ts +165 -0
  124. package/dist/lib/sections.js +327 -0
  125. package/dist/lib/setup.d.ts +82 -0
  126. package/dist/lib/setup.js +150 -0
  127. package/dist/lib/skills.d.ts +8 -0
  128. package/dist/lib/skills.js +12 -0
  129. package/dist/lib/status.d.ts +442 -0
  130. package/dist/lib/status.js +1006 -0
  131. package/dist/lib/sync.d.ts +179 -0
  132. package/dist/lib/sync.js +782 -0
  133. package/dist/lib/target.d.ts +20 -0
  134. package/dist/lib/target.js +27 -0
  135. package/dist/lib/telegram-api.d.ts +541 -0
  136. package/dist/lib/telegram-api.js +1159 -0
  137. package/dist/lib/text-groups.d.ts +89 -0
  138. package/dist/lib/text-groups.js +317 -0
  139. package/dist/lib/thread-cleanup-manager.d.ts +288 -0
  140. package/dist/lib/thread-cleanup-manager.js +560 -0
  141. package/dist/lib/thread-display.d.ts +46 -0
  142. package/dist/lib/thread-display.js +257 -0
  143. package/dist/lib/thread-naming.d.ts +46 -0
  144. package/dist/lib/thread-naming.js +78 -0
  145. package/dist/lib/thread-reconciler.d.ts +239 -0
  146. package/dist/lib/thread-reconciler.js +644 -0
  147. package/dist/lib/threads.d.ts +621 -0
  148. package/dist/lib/threads.js +3679 -0
  149. package/dist/lib/time-injection.d.ts +15 -0
  150. package/dist/lib/time-injection.js +56 -0
  151. package/dist/lib/turns.d.ts +109 -0
  152. package/dist/lib/turns.js +500 -0
  153. package/dist/lib/updates.d.ts +1290 -0
  154. package/dist/lib/updates.js +3634 -0
  155. package/dist/lib/voice.d.ts +117 -0
  156. package/dist/lib/voice.js +174 -0
  157. package/dist/lib/workspace-admission.d.ts +264 -0
  158. package/dist/lib/workspace-admission.js +1136 -0
  159. package/dist/lib/workspace-retirement.d.ts +219 -0
  160. package/dist/lib/workspace-retirement.js +587 -0
  161. package/dist/lib/workspace-slots.d.ts +30 -0
  162. package/dist/lib/workspace-slots.js +54 -0
  163. package/dist/package.json +126 -0
  164. package/dist/pi-telegram/index.js +1 -0
  165. package/dist/skills/generated-control-surface/SKILL.md +107 -0
  166. package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
  167. package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
  168. package/dist/skills/generative-apps/SKILL.md +115 -0
  169. package/dist/skills/show-me/SKILL.md +166 -0
  170. package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
  171. package/dist/skills/telegram-bridge/SKILL.md +129 -0
  172. package/dist/skills/telegram-bridge/references/configuration.md +15 -0
  173. package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
  174. package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
  175. package/docs/architecture.md +1 -1
  176. package/docs/compact-matrix-literal.md +10 -3
  177. package/docs/generative-apps.md +15 -13
  178. package/docs/multi-instance-bus.md +17 -3
  179. package/docs/outbound.md +1 -1
  180. package/docs/public-api.md +2 -2
  181. package/lib/bindings.ts +89 -6
  182. package/lib/bus-follower.ts +11 -3
  183. package/lib/bus-leader.ts +57 -20
  184. package/lib/bus.ts +12 -2
  185. package/lib/commands.ts +2 -2
  186. package/lib/config.ts +14 -7
  187. package/lib/delivery.ts +38 -6
  188. package/lib/extension.ts +26 -1
  189. package/lib/generative-apps.ts +379 -13
  190. package/lib/menu-settings.ts +21 -5
  191. package/lib/outbound-markup.ts +16 -7
  192. package/lib/pi.ts +4 -0
  193. package/lib/sync.ts +3 -0
  194. package/lib/telegram-api.ts +12 -1
  195. package/lib/thread-cleanup-manager.ts +36 -6
  196. package/lib/thread-display.ts +96 -18
  197. package/lib/threads.ts +150 -30
  198. package/package.json +56 -13
  199. package/scripts/build-dist.mjs +44 -0
  200. package/scripts/measure-bus.mjs +8 -1
  201. package/scripts/measure-workspace.mjs +8 -1
  202. package/skills/generative-apps/SKILL.md +1 -1
  203. package/skills/show-me/SKILL.md +1 -1
  204. package/skills/telegram-bridge/SKILL.md +1 -1
@@ -0,0 +1,574 @@
1
+ /**
2
+ * Telegram activity lifecycle normalization and extension dispatch
3
+ * Zones: pi agent lifecycle, extension API, operational delivery
4
+ * Owns stable handler registration, evidence-based activity/source identity, assistant segment and reasoning normalization, ordered public-output projection, executed-tool and compaction events, isolated non-blocking queues, shutdown fencing, diagnostics, and fresh delivery contexts; excludes Pi hook wiring, Telegram rendering implementation, raw transport clients, and consumer-extension behavior
5
+ */
6
+ import { deleteTelegramView, editTelegramView, sendTelegramChatAction, sendTelegramView, } from "./delivery.js";
7
+ const TELEGRAM_ACTIVITY_REGISTRY_KEY = "__piTelegramActivityRegistry__";
8
+ function getOrCreateTelegramActivityRegistry() {
9
+ const globals = globalThis;
10
+ const existing = globals[TELEGRAM_ACTIVITY_REGISTRY_KEY];
11
+ if (existing &&
12
+ typeof existing === "object" &&
13
+ "handlers" in existing &&
14
+ existing.handlers instanceof Map) {
15
+ return existing;
16
+ }
17
+ const registry = { handlers: new Map() };
18
+ globals[TELEGRAM_ACTIVITY_REGISTRY_KEY] = registry;
19
+ return registry;
20
+ }
21
+ export function registerTelegramActivityHandler(registration) {
22
+ const id = registration.id.trim();
23
+ if (!id)
24
+ throw new Error("Telegram activity handler id is required.");
25
+ const registry = getOrCreateTelegramActivityRegistry();
26
+ if (registry.handlers.has(id)) {
27
+ throw new Error(`Telegram activity handler is already registered: ${id}`);
28
+ }
29
+ const handler = {
30
+ ...registration,
31
+ id,
32
+ order: registration.order ?? 0,
33
+ };
34
+ registry.handlers.set(id, handler);
35
+ return () => {
36
+ if (registry.handlers.get(id) === handler)
37
+ registry.handlers.delete(id);
38
+ };
39
+ }
40
+ /** @internal */
41
+ export function clearTelegramActivityHandlers() {
42
+ getOrCreateTelegramActivityRegistry().handlers.clear();
43
+ }
44
+ function getTelegramActivityHandlers() {
45
+ return Array.from(getOrCreateTelegramActivityRegistry().handlers.values()).sort(function (left, right) {
46
+ return left.order - right.order || left.id.localeCompare(right.id);
47
+ });
48
+ }
49
+ function cloneActivityTarget(target) {
50
+ return Object.freeze(target.threadId === undefined
51
+ ? { chatId: target.chatId }
52
+ : { chatId: target.chatId, threadId: target.threadId });
53
+ }
54
+ function createTelegramActivityContext(event, isActive) {
55
+ const defaultScope = event.target
56
+ ? { kind: "target", target: cloneActivityTarget(event.target) }
57
+ : event.source === "telegram"
58
+ ? { kind: "active-turn" }
59
+ : { kind: "instance" };
60
+ const inactive = () => Promise.resolve({
61
+ ok: false,
62
+ reason: "runtime-unavailable",
63
+ message: "Telegram activity context belongs to an inactive session.",
64
+ });
65
+ return {
66
+ activityId: event.activityId,
67
+ sequence: event.sequence,
68
+ source: event.source,
69
+ defaultScope,
70
+ send(view, options) {
71
+ if (!isActive())
72
+ return inactive();
73
+ return sendTelegramView(view, {
74
+ scope: options?.scope ?? defaultScope,
75
+ replyToMessageId: options?.replyToMessageId,
76
+ });
77
+ },
78
+ edit(handle, view) {
79
+ return isActive() ? editTelegramView(handle, view) : inactive();
80
+ },
81
+ delete(handle) {
82
+ return isActive() ? deleteTelegramView(handle) : inactive();
83
+ },
84
+ chatAction(action, options) {
85
+ if (!isActive())
86
+ return inactive();
87
+ return sendTelegramChatAction(action, {
88
+ scope: options?.scope ?? defaultScope,
89
+ });
90
+ },
91
+ };
92
+ }
93
+ function canCoalesceActivityEvents(previous, next) {
94
+ if (previous.activityId !== next.activityId ||
95
+ previous.type !== next.type) {
96
+ return false;
97
+ }
98
+ if (previous.type === "assistant-text-delta" &&
99
+ next.type === "assistant-text-delta") {
100
+ return previous.contentIndex === next.contentIndex;
101
+ }
102
+ if (previous.type === "reasoning-delta" &&
103
+ next.type === "reasoning-delta") {
104
+ return previous.contentIndex === next.contentIndex;
105
+ }
106
+ if (previous.type === "tool-update" && next.type === "tool-update") {
107
+ return previous.toolCallId === next.toolCallId;
108
+ }
109
+ return false;
110
+ }
111
+ function coalesceActivityEvents(previous, next) {
112
+ if (previous.type === "assistant-text-delta" &&
113
+ next.type === "assistant-text-delta") {
114
+ return { ...next, delta: previous.delta + next.delta };
115
+ }
116
+ if (previous.type === "reasoning-delta" &&
117
+ next.type === "reasoning-delta") {
118
+ return { ...next, delta: previous.delta + next.delta };
119
+ }
120
+ return next;
121
+ }
122
+ /** @internal */
123
+ export function createTelegramActivityDispatcher(deps = {}) {
124
+ const queues = new Map();
125
+ let stopped = false;
126
+ const drain = async (queue) => {
127
+ if (queue.running || !queue.active)
128
+ return;
129
+ queue.running = true;
130
+ try {
131
+ while (queue.active) {
132
+ const event = queue.events.shift();
133
+ if (!event)
134
+ break;
135
+ if (getOrCreateTelegramActivityRegistry().handlers.get(queue.registration.id) !== queue.registration) {
136
+ queue.active = false;
137
+ queue.events = [];
138
+ break;
139
+ }
140
+ try {
141
+ await queue.registration.handle(event, createTelegramActivityContext(event, () => queue.active &&
142
+ !stopped &&
143
+ getOrCreateTelegramActivityRegistry().handlers.get(queue.registration.id) === queue.registration));
144
+ }
145
+ catch (error) {
146
+ deps.recordFailure?.(queue.registration.id, event, error);
147
+ }
148
+ }
149
+ }
150
+ finally {
151
+ queue.running = false;
152
+ }
153
+ };
154
+ return {
155
+ dispatch(event) {
156
+ if (stopped)
157
+ return;
158
+ for (const registration of getTelegramActivityHandlers()) {
159
+ let queue = queues.get(registration.id);
160
+ if (!queue || queue.registration !== registration) {
161
+ queue = {
162
+ registration,
163
+ events: [],
164
+ running: false,
165
+ active: true,
166
+ };
167
+ queues.set(registration.id, queue);
168
+ }
169
+ const previous = queue.events.at(-1);
170
+ if (previous && canCoalesceActivityEvents(previous, event)) {
171
+ queue.events[queue.events.length - 1] = coalesceActivityEvents(previous, event);
172
+ }
173
+ else {
174
+ queue.events.push(event);
175
+ }
176
+ queueMicrotask(function () {
177
+ void drain(queue);
178
+ });
179
+ }
180
+ },
181
+ stop() {
182
+ stopped = true;
183
+ for (const queue of queues.values()) {
184
+ queue.active = false;
185
+ queue.events = [];
186
+ }
187
+ queues.clear();
188
+ },
189
+ };
190
+ }
191
+ /** @internal */
192
+ export function createTelegramActivityBridgeRuntime(deps) {
193
+ let generationSequence = 0;
194
+ let runtime;
195
+ const getRuntime = () => runtime;
196
+ return {
197
+ onSessionStart() {
198
+ runtime?.onSessionShutdown();
199
+ runtime = createTelegramActivityRuntime({
200
+ generation: `${deps.generation}:${++generationSequence}`,
201
+ dispatcher: createTelegramActivityDispatcher({
202
+ recordFailure: deps.recordFailure,
203
+ }),
204
+ observeEvent: deps.observeEvent,
205
+ recordObserverFailure: deps.recordFailure
206
+ ? (event, error) => deps.recordFailure("@llblab/pi-telegram/proactive", event, error)
207
+ : undefined,
208
+ now: deps.now,
209
+ });
210
+ },
211
+ recordInputSource(source) {
212
+ getRuntime()?.recordInputSource(source);
213
+ },
214
+ onAgentStart(target, replyToMessageId) {
215
+ getRuntime()?.onAgentStart(target, replyToMessageId);
216
+ },
217
+ onAssistantEvent(event) {
218
+ getRuntime()?.onAssistantEvent(event);
219
+ },
220
+ onAssistantMessageEnd(stopReason) {
221
+ getRuntime()?.onAssistantMessageEnd(stopReason);
222
+ },
223
+ onToolStart(event) {
224
+ getRuntime()?.onToolStart(event);
225
+ },
226
+ onToolUpdate(event) {
227
+ getRuntime()?.onToolUpdate(event);
228
+ },
229
+ onToolEnd(event) {
230
+ getRuntime()?.onToolEnd(event);
231
+ },
232
+ onCompactionStart(reason) {
233
+ getRuntime()?.onCompactionStart(reason);
234
+ },
235
+ onCompactionEnd(reason) {
236
+ getRuntime()?.onCompactionEnd(reason);
237
+ },
238
+ onCompactionAbandoned() {
239
+ getRuntime()?.onCompactionAbandoned();
240
+ },
241
+ onUiPromptStart(kind, title) {
242
+ getRuntime()?.onUiPromptStart(kind, title);
243
+ },
244
+ onUiPromptEnd() {
245
+ getRuntime()?.onUiPromptEnd();
246
+ },
247
+ onAgentEnd() {
248
+ getRuntime()?.onAgentEnd();
249
+ },
250
+ onAgentSettled() {
251
+ getRuntime()?.onAgentSettled();
252
+ },
253
+ onSessionShutdown() {
254
+ runtime?.onSessionShutdown();
255
+ runtime = undefined;
256
+ },
257
+ };
258
+ }
259
+ /** @internal */
260
+ export function createTelegramActivityRuntime(deps) {
261
+ const now = deps.now ?? Date.now;
262
+ let nextActivityNumber = 0;
263
+ let activityId;
264
+ let activitySource = "unknown";
265
+ let activityTarget;
266
+ let activityReplyToMessageId;
267
+ let sequence = 0;
268
+ let pendingInputSource = "unknown";
269
+ let pendingAssistantSegment;
270
+ let compactionInProgress = false;
271
+ let compactionOwnedActivity = false;
272
+ let uiPromptInProgress = false;
273
+ const ensureActivity = (activeTelegramTarget) => {
274
+ if (activityId)
275
+ return activityId;
276
+ nextActivityNumber += 1;
277
+ activityId = `${deps.generation}:${nextActivityNumber}`;
278
+ activitySource = activeTelegramTarget
279
+ ? "telegram"
280
+ : pendingInputSource === "interactive" || pendingInputSource === "rpc"
281
+ ? "local"
282
+ : pendingInputSource === "extension"
283
+ ? "autonomous"
284
+ : "unknown";
285
+ activityTarget = activeTelegramTarget
286
+ ? cloneActivityTarget(activeTelegramTarget)
287
+ : undefined;
288
+ sequence = 0;
289
+ pendingInputSource = "unknown";
290
+ return activityId;
291
+ };
292
+ const emit = (event) => {
293
+ const currentActivityId = ensureActivity();
294
+ sequence += 1;
295
+ const normalizedEvent = {
296
+ ...event,
297
+ activityId: currentActivityId,
298
+ sequence,
299
+ source: activitySource,
300
+ ...(activityTarget ? { target: activityTarget } : {}),
301
+ ...(activityReplyToMessageId !== undefined ? { replyToMessageId: activityReplyToMessageId } : {}),
302
+ timestamp: now(),
303
+ };
304
+ try {
305
+ deps.observeEvent?.(normalizedEvent);
306
+ }
307
+ catch (error) {
308
+ deps.recordObserverFailure?.(normalizedEvent, error);
309
+ }
310
+ deps.dispatcher.dispatch(normalizedEvent);
311
+ };
312
+ const flushPendingSegment = (placement) => {
313
+ const segment = pendingAssistantSegment;
314
+ pendingAssistantSegment = undefined;
315
+ if (!segment?.text.trim())
316
+ return;
317
+ emit({
318
+ type: "assistant-segment",
319
+ contentIndex: segment.contentIndex,
320
+ text: segment.text,
321
+ placement,
322
+ });
323
+ };
324
+ const clearActivity = () => {
325
+ activityId = undefined;
326
+ activitySource = "unknown";
327
+ activityTarget = undefined;
328
+ activityReplyToMessageId = undefined;
329
+ sequence = 0;
330
+ pendingAssistantSegment = undefined;
331
+ compactionInProgress = false;
332
+ compactionOwnedActivity = false;
333
+ uiPromptInProgress = false;
334
+ };
335
+ const abandonCompaction = () => {
336
+ if (!compactionInProgress)
337
+ return;
338
+ const shouldClearActivity = compactionOwnedActivity;
339
+ compactionInProgress = false;
340
+ compactionOwnedActivity = false;
341
+ if (shouldClearActivity)
342
+ clearActivity();
343
+ };
344
+ return {
345
+ recordInputSource(source) {
346
+ pendingInputSource = source;
347
+ },
348
+ onAgentStart(activeTelegramTarget, replyToMessageId) {
349
+ abandonCompaction();
350
+ ensureActivity(activeTelegramTarget);
351
+ activityReplyToMessageId = activitySource === "telegram" ? replyToMessageId : undefined;
352
+ emit({ type: "agent-start" });
353
+ },
354
+ onAssistantEvent(event) {
355
+ if (event.type === "text_start") {
356
+ flushPendingSegment("intermediate");
357
+ return;
358
+ }
359
+ if (event.type === "text_delta") {
360
+ if (!event.delta)
361
+ return;
362
+ emit({
363
+ type: "assistant-text-delta",
364
+ contentIndex: event.contentIndex,
365
+ delta: event.delta,
366
+ });
367
+ return;
368
+ }
369
+ if (event.type === "text_end") {
370
+ pendingAssistantSegment = {
371
+ contentIndex: event.contentIndex,
372
+ text: event.content,
373
+ };
374
+ return;
375
+ }
376
+ if (event.type === "thinking_delta") {
377
+ if (!event.delta)
378
+ return;
379
+ emit({
380
+ type: "reasoning-delta",
381
+ contentIndex: event.contentIndex,
382
+ delta: event.delta,
383
+ });
384
+ return;
385
+ }
386
+ if (event.type === "thinking_end") {
387
+ if (!event.content.trim())
388
+ return;
389
+ emit({
390
+ type: "reasoning-end",
391
+ contentIndex: event.contentIndex,
392
+ text: event.content,
393
+ });
394
+ return;
395
+ }
396
+ if (event.type === "toolcall_start") {
397
+ flushPendingSegment("intermediate");
398
+ return;
399
+ }
400
+ if (event.type === "done") {
401
+ flushPendingSegment("final");
402
+ return;
403
+ }
404
+ if (event.type === "error")
405
+ flushPendingSegment("terminal-partial");
406
+ },
407
+ onAssistantMessageEnd(stopReason) {
408
+ if (stopReason === "aborted")
409
+ pendingAssistantSegment = undefined;
410
+ },
411
+ onToolStart(event) {
412
+ emit({ type: "tool-start", ...event });
413
+ },
414
+ onToolUpdate(event) {
415
+ emit({ type: "tool-update", ...event });
416
+ },
417
+ onToolEnd(event) {
418
+ emit({ type: "tool-end", ...event });
419
+ },
420
+ onCompactionStart(reason) {
421
+ abandonCompaction();
422
+ compactionOwnedActivity = !activityId;
423
+ compactionInProgress = true;
424
+ ensureActivity();
425
+ emit({ type: "compaction-start", reason });
426
+ },
427
+ onCompactionEnd(reason) {
428
+ if (!compactionInProgress || !activityId)
429
+ return;
430
+ const shouldClearActivity = compactionOwnedActivity;
431
+ compactionInProgress = false;
432
+ compactionOwnedActivity = false;
433
+ emit({ type: "compaction-end", reason });
434
+ if (shouldClearActivity)
435
+ clearActivity();
436
+ },
437
+ onCompactionAbandoned() {
438
+ abandonCompaction();
439
+ },
440
+ onUiPromptStart(kind, title) {
441
+ if (!activityId || uiPromptInProgress)
442
+ return;
443
+ uiPromptInProgress = true;
444
+ emit({ type: "ui-prompt-start", kind, title });
445
+ },
446
+ onUiPromptEnd() {
447
+ if (!activityId || !uiPromptInProgress)
448
+ return;
449
+ uiPromptInProgress = false;
450
+ emit({ type: "ui-prompt-end" });
451
+ },
452
+ onAgentEnd() {
453
+ if (activityId)
454
+ emit({ type: "agent-end" });
455
+ },
456
+ onAgentSettled() {
457
+ if (!activityId)
458
+ return;
459
+ flushPendingSegment("terminal-partial");
460
+ emit({ type: "agent-settled" });
461
+ clearActivity();
462
+ },
463
+ onSessionShutdown() {
464
+ pendingInputSource = "unknown";
465
+ clearActivity();
466
+ deps.dispatcher.stop();
467
+ },
468
+ };
469
+ }
470
+ export function createTelegramActivityPublicationRuntime() {
471
+ let generation = 0;
472
+ let tail = Promise.resolve();
473
+ const pending = new Set();
474
+ const reserve = () => {
475
+ const admittedGeneration = generation;
476
+ let state = "pending";
477
+ let resolve;
478
+ const ready = new Promise((accept) => { resolve = accept; });
479
+ const cancel = () => {
480
+ if (state !== "pending")
481
+ return;
482
+ state = "cancelled";
483
+ pending.delete(cancel);
484
+ resolve(undefined);
485
+ };
486
+ pending.add(cancel);
487
+ const result = tail.then(async () => {
488
+ const task = await ready;
489
+ if (admittedGeneration === generation && task)
490
+ await task();
491
+ });
492
+ tail = result.catch(() => { });
493
+ return {
494
+ publish(task) {
495
+ if (state === "cancelled")
496
+ return result;
497
+ if (state === "published")
498
+ return Promise.reject(new Error("Publication reservation already published."));
499
+ state = "published";
500
+ pending.delete(cancel);
501
+ resolve(task);
502
+ return result;
503
+ },
504
+ cancel,
505
+ };
506
+ };
507
+ return {
508
+ reserve,
509
+ enqueue: (task) => reserve().publish(task),
510
+ reset() {
511
+ generation += 1;
512
+ for (const cancel of pending)
513
+ cancel();
514
+ tail = Promise.resolve();
515
+ },
516
+ };
517
+ }
518
+ export function createTelegramAssistantOutputRuntime(deps) {
519
+ let generation = 0;
520
+ let running = false;
521
+ let tail = Promise.resolve();
522
+ const admitted = new Set();
523
+ const isEligibleEvent = (event) => (event.source === "telegram" && event.placement === "intermediate") ||
524
+ event.source === "local" ||
525
+ event.source === "autonomous" ||
526
+ event.source === "unknown";
527
+ return {
528
+ start() {
529
+ generation += 1;
530
+ running = true;
531
+ admitted.clear();
532
+ tail = Promise.resolve();
533
+ },
534
+ accept(event) {
535
+ if (!running || !isEligibleEvent(event) || !event.text.trim())
536
+ return;
537
+ const key = `${event.activityId}:${event.sequence}`;
538
+ if (admitted.has(key))
539
+ return;
540
+ admitted.add(key);
541
+ const admittedGeneration = generation;
542
+ const admittedAuthority = deps.captureAuthority?.();
543
+ const preparation = deps.prepareSend?.(event);
544
+ const enqueue = deps.enqueue ?? ((task) => tail.then(task));
545
+ tail = enqueue(async () => {
546
+ const isAdmittedAuthorityActive = () => running &&
547
+ generation === admittedGeneration &&
548
+ isEligibleEvent(event) &&
549
+ (deps.isAuthorityActive === undefined ||
550
+ deps.isAuthorityActive(admittedAuthority));
551
+ if (!isAdmittedAuthorityActive() || !deps.canDeliver(event))
552
+ return;
553
+ try {
554
+ if (preparation)
555
+ await preparation.wait();
556
+ if (!isAdmittedAuthorityActive() || !deps.canDeliver(event))
557
+ return;
558
+ await deps.send(event, admittedAuthority, isAdmittedAuthorityActive);
559
+ }
560
+ catch (error) {
561
+ deps.recordFailure?.(event, error);
562
+ }
563
+ }).finally(() => preparation?.settle());
564
+ },
565
+ waitForIdle() {
566
+ return tail;
567
+ },
568
+ stop() {
569
+ generation += 1;
570
+ running = false;
571
+ admitted.clear();
572
+ },
573
+ };
574
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Cross-instance Telegram agent message resolution and turn injection
3
+ * Zones: multi-instance bus, inbound routing, operational delivery
4
+ * Owns live target resolution, source attribution, same-target fencing, and synthetic turn construction; excludes Bot API delivery and bus transport.
5
+ */
6
+ import type { TelegramBusAgentMessage, TelegramBusAgentTargetSelector, TelegramBusFollowerRegistry } from "./bus.ts";
7
+ import type { TelegramTarget } from "./target.ts";
8
+ export interface TelegramAgentMessageRuntimeDeps<TContext, TUpdate> {
9
+ instanceId: string;
10
+ getAllowedChatId: () => number | undefined;
11
+ getLeaderTarget: () => TelegramTarget | undefined;
12
+ getLeaderThreadName: () => string | undefined;
13
+ getDisplayTitle?: (target: TelegramTarget) => string | undefined;
14
+ followerRegistry: TelegramBusFollowerRegistry;
15
+ getContext: () => TContext | undefined;
16
+ handleUpdate: (update: TUpdate, ctx: TContext) => Promise<void>;
17
+ getNowMs?: () => number;
18
+ }
19
+ export declare function createTelegramAgentMessageRuntime<TContext, TUpdate>(deps: TelegramAgentMessageRuntimeDeps<TContext, TUpdate>): {
20
+ resolveTarget: (selector: TelegramBusAgentTargetSelector, sourceTarget?: TelegramTarget) => (TelegramTarget & {
21
+ threadId: number;
22
+ }) | undefined;
23
+ route(input: {
24
+ sourceTarget?: TelegramTarget;
25
+ sourceThreadName?: string;
26
+ message: TelegramBusAgentMessage;
27
+ }): Promise<void>;
28
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Cross-instance Telegram agent message resolution and turn injection
3
+ * Zones: multi-instance bus, inbound routing, operational delivery
4
+ * Owns live target resolution, source attribution, same-target fencing, and synthetic turn construction; excludes Bot API delivery and bus transport.
5
+ */
6
+ import { TELEGRAM_INTERNAL_AGENT_MESSAGE } from "./updates.js";
7
+ export function createTelegramAgentMessageRuntime(deps) {
8
+ const getNowMs = deps.getNowMs ?? Date.now;
9
+ const sameTarget = (left, right) => left?.chatId === right?.chatId && left?.threadId === right?.threadId;
10
+ const listLiveTargets = () => {
11
+ const targets = [];
12
+ const leaderTarget = deps.getLeaderTarget();
13
+ if (leaderTarget?.threadId) {
14
+ targets.push({
15
+ target: { chatId: leaderTarget.chatId, threadId: leaderTarget.threadId },
16
+ threadName: deps.getDisplayTitle?.(leaderTarget) ?? deps.getLeaderThreadName(),
17
+ });
18
+ }
19
+ for (const follower of deps.followerRegistry.list()) {
20
+ if (!follower.target?.threadId)
21
+ continue;
22
+ targets.push({
23
+ target: {
24
+ chatId: follower.target.chatId,
25
+ threadId: follower.target.threadId,
26
+ },
27
+ threadName: deps.getDisplayTitle?.(follower.target) ?? follower.threadName,
28
+ });
29
+ }
30
+ return targets;
31
+ };
32
+ const resolveTarget = (selector, sourceTarget) => {
33
+ const allowedChatId = deps.getAllowedChatId();
34
+ const chatId = selector.chatId ?? allowedChatId;
35
+ if (chatId === undefined || chatId !== allowedChatId)
36
+ return undefined;
37
+ const matches = listLiveTargets().filter((candidate) => {
38
+ if (candidate.target.chatId !== chatId)
39
+ return false;
40
+ if (selector.threadId !== undefined) {
41
+ return candidate.target.threadId === selector.threadId;
42
+ }
43
+ const requestedName = selector.threadName?.trim().toLocaleLowerCase();
44
+ return (requestedName !== undefined &&
45
+ requestedName.length > 0 &&
46
+ candidate.threadName?.trim().toLocaleLowerCase() === requestedName);
47
+ });
48
+ if (matches.length !== 1)
49
+ return undefined;
50
+ return sameTarget(matches[0].target, sourceTarget)
51
+ ? undefined
52
+ : matches[0].target;
53
+ };
54
+ return {
55
+ resolveTarget,
56
+ async route(input) {
57
+ const target = resolveTarget({
58
+ chatId: input.message.target.chatId,
59
+ threadId: input.message.target.threadId,
60
+ }, input.sourceTarget);
61
+ if (!target)
62
+ throw new Error("Telegram agent target is no longer live.");
63
+ const allowedChatId = deps.getAllowedChatId();
64
+ const ctx = deps.getContext();
65
+ if (allowedChatId === undefined || !ctx) {
66
+ throw new Error("Telegram agent turn routing is unavailable.");
67
+ }
68
+ const sourceTitle = (input.sourceTarget ? deps.getDisplayTitle?.(input.sourceTarget) : undefined)
69
+ ?? input.sourceThreadName;
70
+ const sourceLabel = sourceTitle
71
+ ?.replace(/[\r\n\[\]]+/g, " ")
72
+ .trim()
73
+ .slice(0, 64) || "Pi";
74
+ const message = {
75
+ message_id: input.message.messageId,
76
+ date: Math.floor(getNowMs() / 1000),
77
+ chat: { id: target.chatId, type: "private" },
78
+ from: { id: allowedChatId, is_bot: false, first_name: "Pi Agent" },
79
+ message_thread_id: target.threadId,
80
+ pi_telegram_agent_source_thread: sourceLabel,
81
+ text: input.message.text,
82
+ };
83
+ await deps.handleUpdate({ message, [TELEGRAM_INTERNAL_AGENT_MESSAGE]: true }, ctx);
84
+ },
85
+ };
86
+ }