@llblab/pi-telegram 0.51.5 → 0.52.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 (181) hide show
  1. package/AGENTS.md +10 -8
  2. package/BACKLOG.md +1 -8
  3. package/CHANGELOG.md +23 -0
  4. package/LICENSE +21 -0
  5. package/README.md +16 -6
  6. package/dist/lib/activity-verbosity.d.ts +1 -5
  7. package/dist/lib/activity-verbosity.js +5 -7
  8. package/dist/lib/bindings.d.ts +9 -2
  9. package/dist/lib/bindings.js +22 -13
  10. package/dist/lib/bus-api.js +4 -2
  11. package/dist/lib/bus-follower.d.ts +55 -57
  12. package/dist/lib/bus-follower.js +213 -140
  13. package/dist/lib/bus-leader.d.ts +21 -2
  14. package/dist/lib/bus-leader.js +181 -25
  15. package/dist/lib/bus-transport.d.ts +4 -1
  16. package/dist/lib/bus-transport.js +28 -4
  17. package/dist/lib/bus.d.ts +52 -56
  18. package/dist/lib/bus.js +78 -246
  19. package/dist/lib/channel-posts.d.ts +1 -3
  20. package/dist/lib/channel-posts.js +23 -26
  21. package/dist/lib/command-templates.d.ts +0 -5
  22. package/dist/lib/command-templates.js +5 -5
  23. package/dist/lib/commands.d.ts +12 -26
  24. package/dist/lib/commands.js +152 -98
  25. package/dist/lib/config.d.ts +0 -17
  26. package/dist/lib/config.js +12 -14
  27. package/dist/lib/delivery.d.ts +0 -4
  28. package/dist/lib/delivery.js +2 -5
  29. package/dist/lib/extension.js +281 -61
  30. package/dist/lib/generative-apps.d.ts +0 -19
  31. package/dist/lib/inbound.d.ts +0 -2
  32. package/dist/lib/inbound.js +2 -2
  33. package/dist/lib/journal.d.ts +177 -16
  34. package/dist/lib/journal.js +1115 -249
  35. package/dist/lib/keyboard.d.ts +0 -2
  36. package/dist/lib/keyboard.js +2 -2
  37. package/dist/lib/lifecycle.d.ts +57 -2
  38. package/dist/lib/lifecycle.js +110 -5
  39. package/dist/lib/locks.d.ts +100 -12
  40. package/dist/lib/locks.js +380 -14
  41. package/dist/lib/logging.d.ts +30 -12
  42. package/dist/lib/logging.js +129 -72
  43. package/dist/lib/media.d.ts +28 -5
  44. package/dist/lib/media.js +26 -8
  45. package/dist/lib/menu-model.d.ts +0 -11
  46. package/dist/lib/menu-model.js +8 -8
  47. package/dist/lib/menu-settings.d.ts +0 -18
  48. package/dist/lib/menu-settings.js +18 -18
  49. package/dist/lib/menu-status.d.ts +4 -4
  50. package/dist/lib/menu-status.js +12 -7
  51. package/dist/lib/menu-thinking.d.ts +1 -1
  52. package/dist/lib/menu-thinking.js +1 -1
  53. package/dist/lib/menu.d.ts +1 -0
  54. package/dist/lib/menu.js +4 -3
  55. package/dist/lib/model.js +2 -4
  56. package/dist/lib/outbound-attachments.d.ts +0 -1
  57. package/dist/lib/outbound-attachments.js +1 -1
  58. package/dist/lib/outbound-buttons.js +1 -8
  59. package/dist/lib/outbound-markup.d.ts +1 -6
  60. package/dist/lib/outbound-markup.js +4 -21
  61. package/dist/lib/outbound.d.ts +0 -8
  62. package/dist/lib/outbound.js +5 -5
  63. package/dist/lib/paths.d.ts +50 -7
  64. package/dist/lib/paths.js +155 -17
  65. package/dist/lib/pi.d.ts +1 -0
  66. package/dist/lib/pi.js +2 -1
  67. package/dist/lib/polling.d.ts +0 -5
  68. package/dist/lib/polling.js +5 -5
  69. package/dist/lib/preview.d.ts +0 -3
  70. package/dist/lib/preview.js +3 -3
  71. package/dist/lib/prompt-templates.d.ts +0 -1
  72. package/dist/lib/prompt-templates.js +1 -1
  73. package/dist/lib/queue.d.ts +3 -15
  74. package/dist/lib/queue.js +69 -8
  75. package/dist/lib/recovery.d.ts +29 -9
  76. package/dist/lib/recovery.js +104 -33
  77. package/dist/lib/replies.d.ts +0 -4
  78. package/dist/lib/replies.js +1 -1
  79. package/dist/lib/routing.d.ts +73 -8
  80. package/dist/lib/routing.js +2206 -307
  81. package/dist/lib/sections.d.ts +0 -2
  82. package/dist/lib/sections.js +1 -1
  83. package/dist/lib/status.d.ts +46 -9
  84. package/dist/lib/status.js +179 -27
  85. package/dist/lib/sync.d.ts +0 -5
  86. package/dist/lib/sync.js +9 -8
  87. package/dist/lib/telegram-api.d.ts +11 -3
  88. package/dist/lib/telegram-api.js +34 -23
  89. package/dist/lib/thread-cleanup-manager.d.ts +1 -0
  90. package/dist/lib/thread-cleanup-manager.js +18 -21
  91. package/dist/lib/thread-naming.d.ts +32 -5
  92. package/dist/lib/thread-naming.js +191 -7
  93. package/dist/lib/thread-reconciler.d.ts +2 -0
  94. package/dist/lib/thread-reconciler.js +1 -1
  95. package/dist/lib/threads.d.ts +292 -59
  96. package/dist/lib/threads.js +1664 -373
  97. package/dist/lib/turns.d.ts +0 -1
  98. package/dist/lib/turns.js +1 -1
  99. package/dist/lib/updates.d.ts +184 -28
  100. package/dist/lib/updates.js +1032 -114
  101. package/dist/lib/wire.d.ts +12 -0
  102. package/dist/lib/wire.js +21 -0
  103. package/dist/lib/workspace-admission.d.ts +25 -17
  104. package/dist/lib/workspace-admission.js +95 -20
  105. package/dist/lib/workspace-identity.d.ts +20 -0
  106. package/dist/lib/workspace-identity.js +103 -0
  107. package/dist/lib/workspace-retirement.d.ts +45 -10
  108. package/dist/lib/workspace-retirement.js +196 -64
  109. package/dist/package.json +6 -6
  110. package/dist/skills/generated-control-surface/SKILL.md +3 -3
  111. package/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
  112. package/docs/README.md +1 -1
  113. package/docs/activity.md +1 -1
  114. package/docs/architecture.md +158 -39
  115. package/docs/callback-namespaces.md +1 -1
  116. package/docs/delivery.md +1 -1
  117. package/docs/generative-apps.md +2 -2
  118. package/docs/inbound.md +1 -1
  119. package/docs/multi-instance-bus.md +127 -19
  120. package/docs/public-api.md +5 -3
  121. package/docs/ui-style.md +26 -8
  122. package/docs/updates.md +1 -1
  123. package/lib/activity-verbosity.ts +5 -9
  124. package/lib/bindings.ts +23 -10
  125. package/lib/bus-api.ts +5 -2
  126. package/lib/bus-follower.ts +251 -226
  127. package/lib/bus-leader.ts +178 -31
  128. package/lib/bus-transport.ts +30 -8
  129. package/lib/bus.ts +110 -334
  130. package/lib/channel-posts.ts +29 -29
  131. package/lib/command-templates.ts +5 -5
  132. package/lib/commands.ts +150 -112
  133. package/lib/config.ts +12 -17
  134. package/lib/delivery.ts +2 -8
  135. package/lib/extension.ts +282 -72
  136. package/lib/generative-apps.ts +0 -22
  137. package/lib/inbound.ts +2 -2
  138. package/lib/journal.ts +1182 -326
  139. package/lib/keyboard.ts +2 -2
  140. package/lib/lifecycle.ts +141 -5
  141. package/lib/locks.ts +402 -23
  142. package/lib/logging.ts +151 -101
  143. package/lib/media.ts +51 -9
  144. package/lib/menu-model.ts +8 -8
  145. package/lib/menu-settings.ts +18 -18
  146. package/lib/menu-status.ts +11 -0
  147. package/lib/menu-thinking.ts +2 -2
  148. package/lib/menu.ts +5 -1
  149. package/lib/model.ts +2 -4
  150. package/lib/outbound-attachments.ts +1 -1
  151. package/lib/outbound-buttons.ts +1 -10
  152. package/lib/outbound-markup.ts +5 -24
  153. package/lib/outbound.ts +5 -5
  154. package/lib/paths.ts +177 -17
  155. package/lib/pi.ts +5 -2
  156. package/lib/polling.ts +5 -5
  157. package/lib/preview.ts +3 -3
  158. package/lib/prompt-templates.ts +1 -1
  159. package/lib/queue.ts +67 -9
  160. package/lib/recovery.ts +104 -44
  161. package/lib/replies.ts +1 -1
  162. package/lib/routing.ts +1955 -375
  163. package/lib/sections.ts +1 -1
  164. package/lib/status.ts +185 -36
  165. package/lib/sync.ts +8 -8
  166. package/lib/telegram-api.ts +48 -33
  167. package/lib/thread-cleanup-manager.ts +24 -21
  168. package/lib/thread-naming.ts +267 -9
  169. package/lib/thread-reconciler.ts +3 -1
  170. package/lib/threads.ts +1718 -491
  171. package/lib/turns.ts +1 -1
  172. package/lib/updates.ts +1072 -151
  173. package/lib/wire.ts +28 -0
  174. package/lib/workspace-admission.ts +97 -47
  175. package/lib/workspace-identity.ts +147 -0
  176. package/lib/workspace-retirement.ts +234 -102
  177. package/package.json +6 -6
  178. package/scripts/audit-exports.mjs +100 -0
  179. package/scripts/check-downgrade.mjs +80 -43
  180. package/skills/generated-control-surface/SKILL.md +3 -3
  181. package/skills/telegram-bridge/references/diagnosis.md +3 -3
package/lib/keyboard.ts CHANGED
@@ -21,9 +21,9 @@ export interface TelegramInlineKeyboardMarkup {
21
21
  inline_keyboard: TelegramInlineKeyboardButton[][];
22
22
  }
23
23
 
24
- export const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
24
+ const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
25
25
 
26
- export function getTelegramCallbackDataByteLength(value: string): number {
26
+ function getTelegramCallbackDataByteLength(value: string): number {
27
27
  return new TextEncoder().encode(value).byteLength;
28
28
  }
29
29
 
package/lib/lifecycle.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  /**
2
- * Telegram lifecycle hook registration helpers
2
+ * Telegram session lifecycle coordination and hook registration
3
3
  * Zones: pi agent lifecycle, telegram session
4
- * Binds prepared Telegram lifecycle runtimes to pi extension lifecycle events
4
+ * Owns context generations, bounded connect intent across resume, and session sequencing.
5
+ * Transport authority, durable bindings, and queue custody remain with their owners.
5
6
  */
6
7
 
8
+ import { randomUUID } from "node:crypto";
9
+ import { formatTelegramConnectionFailure } from "./status.ts";
7
10
  import * as BusFollower from "./bus-follower.ts";
8
11
  import * as Queue from "./queue.ts";
9
12
  import * as TextGroups from "./text-groups.ts";
@@ -157,6 +160,130 @@ export interface TelegramSessionLifecycleHooks {
157
160
  ) => Promise<void>;
158
161
  }
159
162
 
163
+ export interface TelegramConnectionIntent {
164
+ id: string;
165
+ cwd: string;
166
+ profileName?: string;
167
+ }
168
+ interface TelegramConnectionHandoff extends TelegramConnectionIntent {
169
+ pid: number;
170
+ targetSessionFile: string;
171
+ expiresAtMs: number;
172
+ }
173
+ export interface TelegramConnectionHandoffStore {
174
+ pending?: TelegramConnectionHandoff;
175
+ }
176
+ const HANDOFF_KEY = Symbol.for("pi-telegram.connection-resume.v1");
177
+ function processHandoffStore(): TelegramConnectionHandoffStore {
178
+ const globals = globalThis as unknown as Record<symbol, TelegramConnectionHandoffStore>;
179
+ return globals[HANDOFF_KEY] ??= {};
180
+ }
181
+
182
+ export interface TelegramConnectionLifecycle {
183
+ prepare(event: SessionStartEvent, ctx: ExtensionContext): (() => void) | undefined;
184
+ onSessionShutdown(event: SessionShutdownEvent, ctx: ExtensionContext): void;
185
+ }
186
+
187
+ export function createTelegramConnectionLifecycle(deps: {
188
+ intent: ReturnType<typeof createTelegramConnectionIntentRuntime>;
189
+ getGeneration(): number;
190
+ isCurrent(ctx: ExtensionContext): boolean;
191
+ getProfileName(): string | undefined;
192
+ isConnected(): boolean;
193
+ activateProfile(profileName: string | undefined, isCurrent: () => boolean): Promise<boolean>;
194
+ start(ctx: ExtensionContext): Promise<{ ok: boolean; message?: string }>;
195
+ recordError(error: unknown): void;
196
+ }): TelegramConnectionLifecycle {
197
+ return {
198
+ onSessionShutdown(event, ctx) {
199
+ deps.intent.suspend({ reason: event.reason, cwd: ctx.cwd,
200
+ targetSessionFile: event.targetSessionFile, connected: deps.isConnected(),
201
+ profileName: deps.getProfileName() });
202
+ },
203
+ prepare(event, ctx) {
204
+ const intent = deps.intent.resume({ reason: event.reason, cwd: ctx.cwd,
205
+ sessionFile: event.reason === "resume" ? ctx.sessionManager.getSessionFile() : undefined });
206
+ if (!intent) return undefined;
207
+ const generation = deps.getGeneration();
208
+ const isCurrent = () => deps.intent.isActive(intent.id) &&
209
+ generation === deps.getGeneration() && deps.isCurrent(ctx);
210
+ return () => {
211
+ // Startup remains extension-owned background work; no captured command ctx crosses resume.
212
+ void (async () => {
213
+ if (!isCurrent()) return;
214
+ if (!await deps.activateProfile(intent.profileName, isCurrent)) {
215
+ if (isCurrent()) ctx.ui.notify("Telegram profile unavailable. Run /telegram-setup.", "warning");
216
+ return;
217
+ }
218
+ if (!isCurrent()) return;
219
+ const result = await deps.start(ctx);
220
+ if (!isCurrent()) return;
221
+ if (!result.ok) {
222
+ deps.recordError(new Error(result.message ?? "Telegram resume connection failed."));
223
+ ctx.ui.notify(formatTelegramConnectionFailure(result.message), "warning");
224
+ }
225
+ })().catch((error) => {
226
+ deps.recordError(error);
227
+ if (isCurrent()) ctx.ui.notify(formatTelegramConnectionFailure(error), "warning");
228
+ }).finally(() => deps.intent.finish(intent.id));
229
+ };
230
+ },
231
+ };
232
+ }
233
+
234
+ export function createTelegramConnectionIntentRuntime(options: {
235
+ store?: TelegramConnectionHandoffStore;
236
+ now?: () => number;
237
+ pid?: number;
238
+ } = {}) {
239
+ const store = options.store ?? processHandoffStore();
240
+ const now = options.now ?? Date.now;
241
+ const pid = options.pid ?? process.pid;
242
+ let active: TelegramConnectionIntent | undefined;
243
+ return {
244
+ begin(cwd: string, profileName?: string): string {
245
+ store.pending = undefined;
246
+ active = { id: randomUUID(), cwd, profileName };
247
+ return active.id;
248
+ },
249
+ isActive(id: string): boolean {
250
+ return active?.id === id;
251
+ },
252
+ finish(id: string): void {
253
+ if (active?.id === id) active = undefined;
254
+ },
255
+ cancel(): void {
256
+ active = undefined;
257
+ store.pending = undefined;
258
+ },
259
+ suspend(input: {
260
+ reason: string;
261
+ cwd: string;
262
+ targetSessionFile?: string;
263
+ connected: boolean;
264
+ profileName?: string;
265
+ }): void {
266
+ const intent = active ?? (input.connected
267
+ ? { id: randomUUID(), cwd: input.cwd, profileName: input.profileName }
268
+ : undefined);
269
+ active = undefined;
270
+ store.pending = input.reason === "resume" && input.targetSessionFile &&
271
+ intent?.cwd === input.cwd
272
+ ? { ...intent, pid, targetSessionFile: input.targetSessionFile, expiresAtMs: now() + 30_000 }
273
+ : undefined;
274
+ },
275
+ resume(input: { reason: string; cwd: string; sessionFile?: string }): TelegramConnectionIntent | undefined {
276
+ const handoff = store.pending;
277
+ store.pending = undefined;
278
+ if (!handoff || handoff.pid !== pid || now() >= handoff.expiresAtMs ||
279
+ input.reason !== "resume" || input.cwd !== handoff.cwd ||
280
+ input.sessionFile !== handoff.targetSessionFile) return undefined;
281
+ active = { id: randomUUID(), cwd: handoff.cwd, profileName: handoff.profileName };
282
+ return { ...active };
283
+ },
284
+ };
285
+ }
286
+
160
287
  export interface TelegramSessionContextStore<TContext> {
161
288
  get: () => TContext | undefined;
162
289
  getGeneration: () => number;
@@ -250,6 +377,7 @@ export function createTelegramSessionGenerationFence(
250
377
  }
251
378
 
252
379
  export interface TelegramBridgeSessionServiceRuntime {
380
+ connection?: TelegramConnectionLifecycle;
253
381
  resumeGroupedInput(ctx: ExtensionContext): void;
254
382
  suspendGroupedInput(): void;
255
383
  delivery: {
@@ -319,6 +447,7 @@ export interface TelegramBridgeSessionLifecyclePorts<
319
447
  };
320
448
  delivery: TelegramBridgeSessionServiceRuntime["delivery"];
321
449
  polling: TelegramBridgeSessionServiceRuntime["polling"];
450
+ connection?: TelegramBridgeSessionServiceRuntime["connection"];
322
451
  inboundWorker: TelegramBridgeSessionServiceRuntime["inboundWorker"];
323
452
  capabilityMonitor: TelegramBridgeSessionServiceRuntime["capabilityMonitor"];
324
453
  queueWatchdog: TelegramBridgeSessionServiceRuntime["queueWatchdog"];
@@ -348,6 +477,7 @@ export function createTelegramBridgeSessionLifecycleDeps<
348
477
  }),
349
478
  delivery: ports.services.delivery,
350
479
  polling: ports.services.polling,
480
+ connection: ports.services.connection,
351
481
  inboundWorker: ports.services.inboundWorker,
352
482
  capabilityMonitor: ports.services.capabilityMonitor,
353
483
  queueWatchdog: ports.services.queueWatchdog,
@@ -371,19 +501,23 @@ export function createTelegramBridgeSessionLifecycleAssembly<
371
501
  suspendPolling: deps.follower.suspendPolling,
372
502
  recordRuntimeEvent: deps.follower.recordRuntimeEvent,
373
503
  });
504
+ let preserveTarget = true;
374
505
  const queueLifecycle = Queue.createTelegramSessionLifecycleRuntime({
375
506
  ...deps.queue,
376
507
  isSessionActive,
377
- stopPolling: suspendForReplacement,
508
+ stopPolling: () => suspendForReplacement(preserveTarget),
378
509
  clearPendingMediaGroups: deps.services.suspendGroupedInput,
379
510
  });
380
511
  const servicesLifecycle: TelegramSessionLifecycleHooks = {
381
512
  async onSessionStart(event, ctx) {
513
+ const resumeConnection = deps.services.connection?.prepare(event, ctx);
382
514
  await queueLifecycle.onSessionStart(event, ctx);
383
515
  if (!isSessionActive(ctx)) return;
384
516
  deps.services.resumeGroupedInput(ctx);
385
517
  await deps.services.delivery.onSessionStart();
386
- await deps.services.polling.onSessionStart(event, ctx);
518
+ if (!isSessionActive(ctx)) return;
519
+ if (resumeConnection) resumeConnection();
520
+ else await deps.services.polling.onSessionStart(event, ctx);
387
521
  deps.services.capabilityMonitor.start(ctx);
388
522
  deps.services.queueWatchdog.start(ctx);
389
523
  },
@@ -391,6 +525,8 @@ export function createTelegramBridgeSessionLifecycleAssembly<
391
525
  const generation = deps.contextStore.getGeneration();
392
526
  const isCurrent = () => deps.contextStore.isCurrent(ctx, generation);
393
527
  if (!isCurrent()) return;
528
+ deps.services.connection?.onSessionShutdown(event, ctx);
529
+ preserveTarget = event.reason !== "resume";
394
530
  let preserveThread: (() => Promise<void>) | undefined;
395
531
  if (event.reason === "quit") {
396
532
  try {
@@ -515,7 +651,7 @@ export function createTelegramCompactionObserverRuntime<TContext>(
515
651
  "compact",
516
652
  new Error("Compaction observer timed out"),
517
653
  );
518
- deps.onCompactionAbandoned?.();
654
+ // Observer expiry releases local presence, not Pi's eventual terminal result.
519
655
  requestDispatch();
520
656
  }, timeoutMs);
521
657
  unrefTelegramLifecycleTimer(fallbackTimer);