@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,23 @@
1
+ /**
2
+ * Telegram inline-keyboard structural contracts
3
+ * Zones: telegram ui, shared structure
4
+ * Owns the shared Bot API reply-markup shape while feature domains own their button semantics
5
+ */
6
+ export type TelegramInlineKeyboardButtonStyle = "danger" | "success" | "primary";
7
+ export type TelegramInlineKeyboardButton = {
8
+ text: string;
9
+ style?: TelegramInlineKeyboardButtonStyle;
10
+ } & ({
11
+ callback_data: string;
12
+ disabled?: never;
13
+ } | {
14
+ disabled: Record<string, never>;
15
+ callback_data?: never;
16
+ });
17
+ export interface TelegramInlineKeyboardMarkup {
18
+ inline_keyboard: TelegramInlineKeyboardButton[][];
19
+ }
20
+ export declare const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
21
+ export declare function getTelegramCallbackDataByteLength(value: string): number;
22
+ export declare function assertTelegramCallbackData(callbackData: string, context?: string): string;
23
+ export declare function assertTelegramInlineKeyboardCallbackData(replyMarkup: unknown, context?: string): void;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Telegram inline-keyboard structural contracts
3
+ * Zones: telegram ui, shared structure
4
+ * Owns the shared Bot API reply-markup shape while feature domains own their button semantics
5
+ */
6
+ export const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
7
+ export function getTelegramCallbackDataByteLength(value) {
8
+ return new TextEncoder().encode(value).byteLength;
9
+ }
10
+ export function assertTelegramCallbackData(callbackData, context = "Telegram callback_data") {
11
+ const byteLength = getTelegramCallbackDataByteLength(callbackData);
12
+ if (byteLength > TELEGRAM_CALLBACK_DATA_MAX_BYTES) {
13
+ throw new Error(`${context} exceeds ${TELEGRAM_CALLBACK_DATA_MAX_BYTES} bytes (${byteLength}). Use a shorter action/payload or store state behind a compact key.`);
14
+ }
15
+ return callbackData;
16
+ }
17
+ export function assertTelegramInlineKeyboardCallbackData(replyMarkup, context = "Telegram inline keyboard callback_data") {
18
+ if (!replyMarkup || typeof replyMarkup !== "object")
19
+ return;
20
+ const keyboard = replyMarkup
21
+ .inline_keyboard;
22
+ if (!Array.isArray(keyboard))
23
+ return;
24
+ for (const row of keyboard) {
25
+ if (!Array.isArray(row))
26
+ continue;
27
+ for (const button of row) {
28
+ if (!button || typeof button !== "object")
29
+ continue;
30
+ const callbackData = button
31
+ .callback_data;
32
+ if (typeof callbackData !== "string")
33
+ continue;
34
+ assertTelegramCallbackData(callbackData, context);
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Telegram lifecycle hook registration helpers
3
+ * Zones: pi agent lifecycle, telegram session
4
+ * Binds prepared Telegram lifecycle runtimes to pi extension lifecycle events
5
+ */
6
+ import * as BusFollower from "./bus-follower.ts";
7
+ import * as Queue from "./queue.ts";
8
+ import type { AgentEndEvent, AgentSettledEvent, AgentStartEvent, AssistantMessageEvent, BeforeAgentStartEvent, ExtensionAPI, ExtensionContext, InputEvent, MessageEndEvent, SessionBeforeCompactEvent, SessionCompactEvent, SessionCompactFailedEvent, SessionShutdownEvent, SessionStartEvent, ToolExecutionEndEvent, ToolExecutionStartEvent, ToolExecutionUpdateEvent, UIPromptEndEvent, UIPromptStartEvent } from "./pi.ts";
9
+ export declare function setResetTransportReplyDedup(fn: () => void): void;
10
+ export declare function createAgentStartDedupHook(inner: (event: AgentStartEvent, ctx: ExtensionContext) => Promise<void>, schedulePublication?: (task: () => Promise<void>) => void): (event: AgentStartEvent, ctx: ExtensionContext) => Promise<void>;
11
+ type TelegramBeforeAgentStartEvent = Omit<BeforeAgentStartEvent, "systemPrompt"> & {
12
+ systemPrompt: string | string[];
13
+ };
14
+ export interface TelegramBeforeAgentStartResult {
15
+ systemPrompt?: string | string[];
16
+ }
17
+ type TelegramBeforeAgentStartReturn = Promise<TelegramBeforeAgentStartResult | undefined> | TelegramBeforeAgentStartResult | undefined;
18
+ type TelegramLifecycleModel = ExtensionContext["model"];
19
+ type TelegramLifecycleMessage = AgentEndEvent["messages"][number];
20
+ export interface TelegramLifecycleRegistrationDeps {
21
+ isSessionActive?: (ctx: ExtensionContext) => boolean;
22
+ onInput?: (event: InputEvent, ctx: ExtensionContext) => Promise<void> | void;
23
+ onSessionStart: (event: SessionStartEvent, ctx: ExtensionContext) => Promise<void>;
24
+ onSessionShutdown: (event: SessionShutdownEvent, ctx: ExtensionContext) => Promise<void>;
25
+ onSessionBeforeCompact?: (event: SessionBeforeCompactEvent, ctx: ExtensionContext) => Promise<void> | void;
26
+ onSessionCompact?: (event: SessionCompactEvent, ctx: ExtensionContext) => Promise<void> | void;
27
+ onSessionCompactFailed?: (event: SessionCompactFailedEvent, ctx: ExtensionContext) => Promise<void> | void;
28
+ onBeforeAgentStart: (event: TelegramBeforeAgentStartEvent, ctx: ExtensionContext) => TelegramBeforeAgentStartReturn;
29
+ onModelSelect: (event: {
30
+ model: TelegramLifecycleModel;
31
+ }, ctx: ExtensionContext) => Promise<void> | void;
32
+ onAgentStart: (event: AgentStartEvent, ctx: ExtensionContext) => Promise<void>;
33
+ onToolExecutionStart: (event: ToolExecutionStartEvent, ctx: ExtensionContext) => Promise<void> | void;
34
+ onToolExecutionUpdate?: (event: ToolExecutionUpdateEvent, ctx: ExtensionContext) => Promise<void> | void;
35
+ onToolExecutionEnd: (event: ToolExecutionEndEvent, ctx: ExtensionContext) => Promise<void> | void;
36
+ onMessageStart: (event: {
37
+ message: TelegramLifecycleMessage;
38
+ }, ctx: ExtensionContext) => Promise<void>;
39
+ onMessageUpdate: (event: {
40
+ message: TelegramLifecycleMessage;
41
+ assistantMessageEvent?: AssistantMessageEvent;
42
+ }, ctx: ExtensionContext) => Promise<void>;
43
+ onMessageEnd?: (event: MessageEndEvent, ctx: ExtensionContext) => Promise<void> | void;
44
+ onUiPromptStart?: (event: UIPromptStartEvent, ctx: ExtensionContext) => Promise<void> | void;
45
+ onUiPromptEnd?: (event: UIPromptEndEvent, ctx: ExtensionContext) => Promise<void> | void;
46
+ onAgentEnd: (event: AgentEndEvent, ctx: ExtensionContext) => Promise<void>;
47
+ onAgentSettled?: (event: AgentSettledEvent, ctx: ExtensionContext) => Promise<void> | void;
48
+ }
49
+ export interface TelegramSessionLifecycleHooks {
50
+ onSessionStart: (event: SessionStartEvent, ctx: ExtensionContext) => Promise<void>;
51
+ onSessionShutdown: (event: SessionShutdownEvent, ctx: ExtensionContext) => Promise<void>;
52
+ }
53
+ export interface TelegramSessionContextStore<TContext> {
54
+ get: () => TContext | undefined;
55
+ getGeneration: () => number;
56
+ isCurrent: (ctx: TContext, generation?: number) => boolean;
57
+ set: (ctx: TContext) => number;
58
+ clear: (ctx?: TContext) => boolean;
59
+ }
60
+ export declare function createTelegramSessionContextStore<TContext>(options?: {
61
+ getIdentity?: (ctx: TContext) => unknown;
62
+ }): TelegramSessionContextStore<TContext>;
63
+ export declare function createTelegramSessionGenerationFence(store: TelegramSessionContextStore<ExtensionContext>, hooks: TelegramSessionLifecycleHooks): TelegramSessionLifecycleHooks;
64
+ export interface TelegramBridgeSessionServiceRuntime {
65
+ resumeGroupedInput(ctx: ExtensionContext): void;
66
+ suspendGroupedInput(): void;
67
+ delivery: {
68
+ onSessionStart(): Promise<void>;
69
+ onSessionShutdown(): Promise<void>;
70
+ };
71
+ polling: {
72
+ onSessionStart(event: SessionStartEvent, ctx: ExtensionContext): Promise<void>;
73
+ };
74
+ inboundWorker: {
75
+ onSessionShutdown(): Promise<void>;
76
+ };
77
+ capabilityMonitor: {
78
+ start(ctx: ExtensionContext): void;
79
+ stop(): void;
80
+ };
81
+ queueWatchdog: {
82
+ start(ctx: ExtensionContext): void;
83
+ stop(): void;
84
+ };
85
+ guestPlaceholder?: {
86
+ stopAll(): void;
87
+ };
88
+ }
89
+ export interface TelegramBridgeSessionLifecycleAssemblyDeps<TQueueItem, TModel = unknown> {
90
+ contextStore: TelegramSessionContextStore<ExtensionContext>;
91
+ queue: Omit<Queue.TelegramSessionLifecycleRuntimeDeps<ExtensionContext, TQueueItem, TModel>, "isSessionActive" | "stopPolling" | "clearPendingMediaGroups">;
92
+ follower: Omit<BusFollower.TelegramBusFollowerSessionRefreshHookDeps<ExtensionContext>, "isSessionActive"> & BusFollower.TelegramBusFollowerSessionReplacementSuspenderDeps;
93
+ services: TelegramBridgeSessionServiceRuntime;
94
+ }
95
+ export interface TelegramBridgeSessionLifecyclePorts<TQueueItem, TModel = unknown> {
96
+ contextStore: TelegramSessionContextStore<ExtensionContext>;
97
+ queue: Omit<Queue.TelegramSessionLifecycleRuntimeDeps<ExtensionContext, TQueueItem, TModel>, "isSessionActive" | "stopPolling" | "clearPendingMediaGroups">;
98
+ follower: Omit<BusFollower.TelegramBusFollowerSessionRefreshHookDeps<ExtensionContext>, "isSessionActive"> & BusFollower.TelegramBusFollowerSessionReplacementSuspenderDeps;
99
+ services: {
100
+ mediaGroup: {
101
+ resume(ctx: ExtensionContext): void;
102
+ suspend(): void;
103
+ };
104
+ textGroup: {
105
+ resume(ctx: ExtensionContext): void;
106
+ suspend(): void;
107
+ };
108
+ delivery: TelegramBridgeSessionServiceRuntime["delivery"];
109
+ polling: TelegramBridgeSessionServiceRuntime["polling"];
110
+ inboundWorker: TelegramBridgeSessionServiceRuntime["inboundWorker"];
111
+ capabilityMonitor: TelegramBridgeSessionServiceRuntime["capabilityMonitor"];
112
+ queueWatchdog: TelegramBridgeSessionServiceRuntime["queueWatchdog"];
113
+ guestPlaceholder?: TelegramBridgeSessionServiceRuntime["guestPlaceholder"];
114
+ };
115
+ }
116
+ export declare function createTelegramBridgeSessionLifecycleDeps<TQueueItem, TModel = unknown>(ports: TelegramBridgeSessionLifecyclePorts<TQueueItem, TModel>): TelegramBridgeSessionLifecycleAssemblyDeps<TQueueItem, TModel>;
117
+ export declare function createTelegramBridgeSessionLifecycleAssembly<TQueueItem, TModel = unknown>(deps: TelegramBridgeSessionLifecycleAssemblyDeps<TQueueItem, TModel>): TelegramSessionLifecycleHooks;
118
+ type TelegramLifecycleTimer = number | ReturnType<typeof setTimeout>;
119
+ export interface TelegramCompactionObserverRuntimeDeps<TContext> {
120
+ isContextActive?: (ctx: TContext) => boolean;
121
+ setCompactionInProgress: (inProgress: boolean) => void;
122
+ updateStatus: (ctx: TContext) => void;
123
+ startTypingLoop?: (ctx: TContext) => boolean | void;
124
+ stopTypingLoop?: () => void;
125
+ requestDeferredDispatchNextQueuedTelegramTurn: (dispatch: (ctx: TContext) => void) => void;
126
+ dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
127
+ recordRuntimeEvent?: (category: string, error: unknown) => void;
128
+ onCompactionAbandoned?: () => void;
129
+ timeoutMs?: number;
130
+ setTimer?: (callback: () => void, ms: number) => TelegramLifecycleTimer;
131
+ clearTimer?: (timer: TelegramLifecycleTimer) => void;
132
+ }
133
+ export interface TelegramCompactionObserverRuntime<TContext> {
134
+ onSessionBeforeCompact: (event: SessionBeforeCompactEvent, ctx: TContext) => void;
135
+ onSessionCompact: (event: SessionCompactEvent, ctx: TContext) => void;
136
+ onSessionCompactFailed: (event: SessionCompactFailedEvent, ctx: TContext) => void;
137
+ onSessionShutdown: () => void;
138
+ }
139
+ export declare function createTelegramCompactionObserverRuntime<TContext>(deps: TelegramCompactionObserverRuntimeDeps<TContext>): TelegramCompactionObserverRuntime<TContext>;
140
+ export interface TelegramMessageActivityTypingDeps<TContext> {
141
+ hasActiveTurn: () => boolean;
142
+ startTypingLoop: (ctx: TContext) => void;
143
+ onMessageStart: TelegramLifecycleRegistrationDeps["onMessageStart"];
144
+ onMessageUpdate: TelegramLifecycleRegistrationDeps["onMessageUpdate"];
145
+ recordRuntimeEvent?: (category: string, error: unknown, details?: Record<string, unknown>) => void;
146
+ }
147
+ export declare function createTelegramMessageActivityTypingHooks<TContext extends ExtensionContext>(deps: TelegramMessageActivityTypingDeps<TContext>): Pick<TelegramLifecycleRegistrationDeps, "onMessageStart" | "onMessageUpdate">;
148
+ export declare function createDedupAgentStartHook(dedup: {
149
+ reset(): void;
150
+ }, inner: (event: AgentStartEvent, ctx: ExtensionContext) => Promise<void>): (event: AgentStartEvent, ctx: ExtensionContext) => Promise<void>;
151
+ export interface TelegramExtraLifecycleHooks {
152
+ onSessionStart?: (event: SessionStartEvent, ctx: ExtensionContext) => Promise<void>;
153
+ onSessionShutdown?: (event: SessionShutdownEvent, ctx: ExtensionContext) => Promise<void>;
154
+ }
155
+ export declare function appendTelegramLifecycleHooks(base: TelegramSessionLifecycleHooks, extra: TelegramExtraLifecycleHooks, isSessionActive?: (ctx: ExtensionContext) => boolean): TelegramSessionLifecycleHooks;
156
+ export declare function registerTelegramLifecycleHooks(pi: ExtensionAPI, deps: TelegramLifecycleRegistrationDeps): void;
157
+ export {};
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Telegram lifecycle hook registration helpers
3
+ * Zones: pi agent lifecycle, telegram session
4
+ * Binds prepared Telegram lifecycle runtimes to pi extension lifecycle events
5
+ */
6
+ import * as BusFollower from "./bus-follower.js";
7
+ import * as Queue from "./queue.js";
8
+ import * as TextGroups from "./text-groups.js";
9
+ let resetTransportReplyDedupFn;
10
+ export function setResetTransportReplyDedup(fn) {
11
+ resetTransportReplyDedupFn = fn;
12
+ }
13
+ export function createAgentStartDedupHook(inner, schedulePublication) {
14
+ return async (event, ctx) => {
15
+ const reset = resetTransportReplyDedupFn;
16
+ if (reset) {
17
+ // A new turn must not erase the anchor of a final still ahead in the FIFO.
18
+ if (schedulePublication)
19
+ schedulePublication(async () => { reset(); });
20
+ else
21
+ reset();
22
+ }
23
+ return inner(event, ctx);
24
+ };
25
+ }
26
+ export function createTelegramSessionContextStore(options = {}) {
27
+ let currentContext;
28
+ let currentIdentity;
29
+ let generation = 0;
30
+ const objectIdentityGenerations = new WeakMap();
31
+ const primitiveIdentityGenerations = new Map();
32
+ const resolveIdentity = (ctx) => options.getIdentity?.(ctx) ?? ctx;
33
+ const getIdentityGeneration = (identity) => (typeof identity === "object" && identity !== null) ||
34
+ typeof identity === "function"
35
+ ? objectIdentityGenerations.get(identity)
36
+ : primitiveIdentityGenerations.get(identity);
37
+ const setIdentityGeneration = (identity, value) => {
38
+ if ((typeof identity === "object" && identity !== null) ||
39
+ typeof identity === "function") {
40
+ objectIdentityGenerations.set(identity, value);
41
+ }
42
+ else {
43
+ primitiveIdentityGenerations.set(identity, value);
44
+ }
45
+ };
46
+ return {
47
+ get: () => currentContext,
48
+ getGeneration: () => generation,
49
+ isCurrent: (ctx, expectedGeneration) => {
50
+ if (currentContext === undefined)
51
+ return false;
52
+ const identity = resolveIdentity(ctx);
53
+ return (identity === currentIdentity &&
54
+ getIdentityGeneration(identity) === generation &&
55
+ (expectedGeneration === undefined || generation === expectedGeneration));
56
+ },
57
+ set: (ctx) => {
58
+ currentContext = ctx;
59
+ currentIdentity = resolveIdentity(ctx);
60
+ generation += 1;
61
+ setIdentityGeneration(currentIdentity, generation);
62
+ return generation;
63
+ },
64
+ clear: (ctx) => {
65
+ if (ctx !== undefined) {
66
+ const identity = resolveIdentity(ctx);
67
+ if (identity !== currentIdentity ||
68
+ getIdentityGeneration(identity) !== generation) {
69
+ return false;
70
+ }
71
+ }
72
+ if (currentContext === undefined)
73
+ return false;
74
+ currentContext = undefined;
75
+ currentIdentity = undefined;
76
+ generation += 1;
77
+ return true;
78
+ },
79
+ };
80
+ }
81
+ export function createTelegramSessionGenerationFence(store, hooks) {
82
+ return {
83
+ async onSessionStart(event, ctx) {
84
+ const generation = store.set(ctx);
85
+ await hooks.onSessionStart(event, ctx);
86
+ if (!store.isCurrent(ctx, generation))
87
+ return;
88
+ },
89
+ async onSessionShutdown(event, ctx) {
90
+ const generation = store.getGeneration();
91
+ if (!store.isCurrent(ctx, generation))
92
+ return;
93
+ await hooks.onSessionShutdown(event, ctx);
94
+ store.clear(ctx);
95
+ },
96
+ };
97
+ }
98
+ export function createTelegramBridgeSessionLifecycleDeps(ports) {
99
+ return {
100
+ contextStore: ports.contextStore,
101
+ queue: ports.queue,
102
+ follower: ports.follower,
103
+ services: {
104
+ resumeGroupedInput(ctx) {
105
+ ports.services.mediaGroup.resume(ctx);
106
+ ports.services.textGroup.resume(ctx);
107
+ },
108
+ suspendGroupedInput: TextGroups.createTelegramGroupedInputClearer({
109
+ clearMediaGroups: ports.services.mediaGroup.suspend,
110
+ clearTextGroups: ports.services.textGroup.suspend,
111
+ }),
112
+ delivery: ports.services.delivery,
113
+ polling: ports.services.polling,
114
+ inboundWorker: ports.services.inboundWorker,
115
+ capabilityMonitor: ports.services.capabilityMonitor,
116
+ queueWatchdog: ports.services.queueWatchdog,
117
+ guestPlaceholder: ports.services.guestPlaceholder,
118
+ },
119
+ };
120
+ }
121
+ export function createTelegramBridgeSessionLifecycleAssembly(deps) {
122
+ const isSessionActive = deps.contextStore.isCurrent;
123
+ const suspendForReplacement = BusFollower.createTelegramBusFollowerSessionReplacementSuspender({
124
+ registrationState: deps.follower.registrationState,
125
+ instanceId: deps.follower.instanceId,
126
+ suspendPolling: deps.follower.suspendPolling,
127
+ recordRuntimeEvent: deps.follower.recordRuntimeEvent,
128
+ });
129
+ const queueLifecycle = Queue.createTelegramSessionLifecycleRuntime({
130
+ ...deps.queue,
131
+ isSessionActive,
132
+ stopPolling: suspendForReplacement,
133
+ clearPendingMediaGroups: deps.services.suspendGroupedInput,
134
+ });
135
+ const servicesLifecycle = {
136
+ async onSessionStart(event, ctx) {
137
+ await queueLifecycle.onSessionStart(event, ctx);
138
+ if (!isSessionActive(ctx))
139
+ return;
140
+ deps.services.resumeGroupedInput(ctx);
141
+ await deps.services.delivery.onSessionStart();
142
+ await deps.services.polling.onSessionStart(event, ctx);
143
+ deps.services.capabilityMonitor.start(ctx);
144
+ deps.services.queueWatchdog.start(ctx);
145
+ },
146
+ async onSessionShutdown(event, ctx) {
147
+ if (!isSessionActive(ctx))
148
+ return;
149
+ deps.services.guestPlaceholder?.stopAll();
150
+ await deps.services.delivery.onSessionShutdown();
151
+ if (!isSessionActive(ctx))
152
+ return;
153
+ deps.services.queueWatchdog.stop();
154
+ deps.services.capabilityMonitor.stop();
155
+ await queueLifecycle.onSessionShutdown(event, ctx);
156
+ if (!isSessionActive(ctx))
157
+ return;
158
+ await deps.services.inboundWorker.onSessionShutdown();
159
+ },
160
+ };
161
+ const followerLifecycle = appendTelegramLifecycleHooks(servicesLifecycle, {
162
+ onSessionStart: BusFollower.createTelegramBusFollowerSessionRefreshHook({
163
+ registrationState: deps.follower.registrationState,
164
+ registrationRuntime: deps.follower.registrationRuntime,
165
+ getLeaderState: deps.follower.getLeaderState,
166
+ isSessionActive,
167
+ updateStatus: deps.follower.updateStatus,
168
+ recordRuntimeEvent: deps.follower.recordRuntimeEvent,
169
+ }),
170
+ }, isSessionActive);
171
+ return createTelegramSessionGenerationFence(deps.contextStore, followerLifecycle);
172
+ }
173
+ function unrefTelegramLifecycleTimer(timer) {
174
+ if (!timer || typeof timer !== "object")
175
+ return;
176
+ if (typeof timer.unref === "function")
177
+ timer.unref();
178
+ }
179
+ export function createTelegramCompactionObserverRuntime(deps) {
180
+ const timeoutMs = deps.timeoutMs ?? 300_000;
181
+ const setTimer = deps.setTimer ?? setTimeout;
182
+ const clearTimer = deps.clearTimer ?? clearTimeout;
183
+ let fallbackTimer;
184
+ let observationGeneration = 0;
185
+ let typingStartedByObserver = false;
186
+ const clearFallbackTimer = () => {
187
+ observationGeneration += 1;
188
+ if (!fallbackTimer)
189
+ return;
190
+ clearTimer(fallbackTimer);
191
+ fallbackTimer = undefined;
192
+ };
193
+ const requestDispatch = () => {
194
+ deps.requestDeferredDispatchNextQueuedTelegramTurn(deps.dispatchNextQueuedTelegramTurn);
195
+ };
196
+ return {
197
+ onSessionBeforeCompact: (_event, ctx) => {
198
+ if (deps.isContextActive && !deps.isContextActive(ctx))
199
+ return;
200
+ deps.setCompactionInProgress(true);
201
+ const typingStartResult = deps.startTypingLoop?.(ctx);
202
+ typingStartedByObserver =
203
+ !!deps.startTypingLoop && typingStartResult !== false;
204
+ deps.updateStatus(ctx);
205
+ clearFallbackTimer();
206
+ const admittedGeneration = observationGeneration;
207
+ fallbackTimer = setTimer(() => {
208
+ if (observationGeneration !== admittedGeneration)
209
+ return;
210
+ observationGeneration += 1;
211
+ fallbackTimer = undefined;
212
+ if (deps.isContextActive && !deps.isContextActive(ctx))
213
+ return;
214
+ deps.setCompactionInProgress(false);
215
+ if (typingStartedByObserver)
216
+ deps.stopTypingLoop?.();
217
+ typingStartedByObserver = false;
218
+ deps.updateStatus(ctx);
219
+ deps.recordRuntimeEvent?.("compact", new Error("Compaction observer timed out"));
220
+ deps.onCompactionAbandoned?.();
221
+ requestDispatch();
222
+ }, timeoutMs);
223
+ unrefTelegramLifecycleTimer(fallbackTimer);
224
+ },
225
+ onSessionCompact: (_event, ctx) => {
226
+ if (deps.isContextActive && !deps.isContextActive(ctx))
227
+ return;
228
+ clearFallbackTimer();
229
+ deps.setCompactionInProgress(false);
230
+ if (typingStartedByObserver)
231
+ deps.stopTypingLoop?.();
232
+ typingStartedByObserver = false;
233
+ deps.updateStatus(ctx);
234
+ requestDispatch();
235
+ },
236
+ onSessionCompactFailed: (_event, ctx) => {
237
+ if (deps.isContextActive && !deps.isContextActive(ctx))
238
+ return;
239
+ clearFallbackTimer();
240
+ deps.setCompactionInProgress(false);
241
+ if (typingStartedByObserver)
242
+ deps.stopTypingLoop?.();
243
+ typingStartedByObserver = false;
244
+ deps.updateStatus(ctx);
245
+ deps.onCompactionAbandoned?.();
246
+ requestDispatch();
247
+ },
248
+ onSessionShutdown: () => {
249
+ clearFallbackTimer();
250
+ if (typingStartedByObserver)
251
+ deps.stopTypingLoop?.();
252
+ typingStartedByObserver = false;
253
+ },
254
+ };
255
+ }
256
+ export function createTelegramMessageActivityTypingHooks(deps) {
257
+ const ensureTyping = (ctx) => {
258
+ if (deps.hasActiveTurn())
259
+ deps.startTypingLoop(ctx);
260
+ };
261
+ const handleMessageActivity = async (phase, event, ctx, inner) => {
262
+ const typedCtx = ctx;
263
+ ensureTyping(typedCtx);
264
+ try {
265
+ await inner(event, ctx);
266
+ }
267
+ catch (error) {
268
+ deps.recordRuntimeEvent?.("message-activity", error, { phase });
269
+ }
270
+ finally {
271
+ ensureTyping(typedCtx);
272
+ }
273
+ };
274
+ return {
275
+ onMessageStart: (event, ctx) => handleMessageActivity("start", event, ctx, deps.onMessageStart),
276
+ onMessageUpdate: (event, ctx) => handleMessageActivity("update", event, ctx, deps.onMessageUpdate),
277
+ };
278
+ }
279
+ export function createDedupAgentStartHook(dedup, inner) {
280
+ return async (event, ctx) => {
281
+ dedup.reset();
282
+ await inner(event, ctx);
283
+ };
284
+ }
285
+ export function appendTelegramLifecycleHooks(base, extra, isSessionActive) {
286
+ return {
287
+ onSessionStart: async (event, ctx) => {
288
+ await base.onSessionStart(event, ctx);
289
+ if (isSessionActive?.(ctx) === false)
290
+ return;
291
+ await extra.onSessionStart?.(event, ctx);
292
+ },
293
+ onSessionShutdown: async (event, ctx) => {
294
+ await base.onSessionShutdown(event, ctx);
295
+ if (isSessionActive?.(ctx) === false)
296
+ return;
297
+ await extra.onSessionShutdown?.(event, ctx);
298
+ },
299
+ };
300
+ }
301
+ export function registerTelegramLifecycleHooks(pi, deps) {
302
+ const isActive = (ctx) => deps.isSessionActive?.(ctx) !== false;
303
+ pi.on("input", async (event, ctx) => {
304
+ if (!isActive(ctx))
305
+ return;
306
+ await deps.onInput?.(event, ctx);
307
+ });
308
+ pi.on("session_start", async (event, ctx) => {
309
+ await deps.onSessionStart(event, ctx);
310
+ });
311
+ pi.on("session_shutdown", async (event, ctx) => {
312
+ await deps.onSessionShutdown(event, ctx);
313
+ });
314
+ pi.on("session_before_compact", async (event, ctx) => {
315
+ if (!isActive(ctx))
316
+ return;
317
+ await deps.onSessionBeforeCompact?.(event, ctx);
318
+ });
319
+ pi.on("session_compact", async (event, ctx) => {
320
+ if (!isActive(ctx))
321
+ return;
322
+ await deps.onSessionCompact?.(event, ctx);
323
+ });
324
+ pi.on("session_compact_failed", async (event, ctx) => {
325
+ if (!isActive(ctx))
326
+ return;
327
+ await deps.onSessionCompactFailed?.(event, ctx);
328
+ });
329
+ // The Pi SDK still types this result as a string; compatible runtimes may
330
+ // preserve ordered system prompt blocks through the same public hook.
331
+ const registerBeforeAgentStart = pi.on.bind(pi);
332
+ registerBeforeAgentStart("before_agent_start", async (event, ctx) => {
333
+ return deps.onBeforeAgentStart(event, ctx);
334
+ });
335
+ pi.on("model_select", async (event, ctx) => {
336
+ if (!isActive(ctx))
337
+ return;
338
+ await deps.onModelSelect(event, ctx);
339
+ });
340
+ pi.on("agent_start", async (event, ctx) => {
341
+ if (!isActive(ctx))
342
+ return;
343
+ await deps.onAgentStart(event, ctx);
344
+ });
345
+ pi.on("tool_execution_start", async (event, ctx) => {
346
+ if (!isActive(ctx))
347
+ return;
348
+ await deps.onToolExecutionStart(event, ctx);
349
+ });
350
+ pi.on("tool_execution_update", async (event, ctx) => {
351
+ if (!isActive(ctx))
352
+ return;
353
+ await deps.onToolExecutionUpdate?.(event, ctx);
354
+ });
355
+ pi.on("tool_execution_end", async (event, ctx) => {
356
+ if (!isActive(ctx))
357
+ return;
358
+ await deps.onToolExecutionEnd(event, ctx);
359
+ });
360
+ pi.on("message_start", async (event, ctx) => {
361
+ if (!isActive(ctx))
362
+ return;
363
+ await deps.onMessageStart(event, ctx);
364
+ });
365
+ pi.on("message_update", async (event, ctx) => {
366
+ if (!isActive(ctx))
367
+ return;
368
+ await deps.onMessageUpdate(event, ctx);
369
+ });
370
+ pi.on("message_end", async (event, ctx) => {
371
+ if (!isActive(ctx))
372
+ return;
373
+ await deps.onMessageEnd?.(event, ctx);
374
+ });
375
+ pi.on("ui_prompt_start", async (event, ctx) => {
376
+ if (!isActive(ctx))
377
+ return;
378
+ await deps.onUiPromptStart?.(event, ctx);
379
+ });
380
+ pi.on("ui_prompt_end", async (event, ctx) => {
381
+ if (!isActive(ctx))
382
+ return;
383
+ await deps.onUiPromptEnd?.(event, ctx);
384
+ });
385
+ pi.on("agent_end", async (event, ctx) => {
386
+ if (!isActive(ctx))
387
+ return;
388
+ await deps.onAgentEnd(event, ctx);
389
+ });
390
+ pi.on("agent_settled", async (event, ctx) => {
391
+ if (!isActive(ctx))
392
+ return;
393
+ await deps.onAgentSettled?.(event, ctx);
394
+ });
395
+ }