@llblab/pi-telegram 0.36.9 → 0.36.10

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  > Each release keeps at most 8 outcome records of at most 512 characters.
4
4
 
5
+ ## 0.36.10: Transport-Fenced Typing Hotfix
6
+
7
+ - `Typing Authority Fence`: Starts and continues Telegram typing activity only while the Pi instance has direct ownership or a live follower registration, stopping quietly when authority disappears so classic takeover cannot re-arm a stale loop or flood diagnostics with expected follower-registration errors.
8
+
5
9
  ## 0.36.9: Telegram Status And Generated Controls Hotfix
6
10
 
7
11
  - `Classic Takeover Status`: Keeps a classic-mode client visibly `disconnected` after another Pi instance takes Telegram ownership, preventing stale transport-side activity errors from overriding the authoritative connection state.
package/index.ts CHANGED
@@ -465,6 +465,15 @@ export default function (pi: Pi.ExtensionAPI) {
465
465
  BusApi.createTelegramAggregateTypingActionSender(telegramApiRuntime),
466
466
  updateStatus,
467
467
  isContextActive: telegramSessionContextStore.isCurrent,
468
+ getTransportAuthority() {
469
+ if (ownsTelegramDirectDelivery()) {
470
+ const epoch = getCurrentLeaderEpoch();
471
+ return epoch === undefined ? undefined : `direct:${epoch}`;
472
+ }
473
+ if (!telegramBusFollowerRegistrationState.isRegistered()) return undefined;
474
+ const generation = telegramBusFollowerRegistrationState.getGeneration();
475
+ return generation ? `follower:${generation}` : undefined;
476
+ },
468
477
  recordRuntimeEvent,
469
478
  });
470
479
  const currentModelRuntime = Model.createCurrentModelRuntime({
package/lib/runtime.ts CHANGED
@@ -320,6 +320,8 @@ export interface TelegramTypingLoopDeps {
320
320
  options?: { message_thread_id?: number },
321
321
  ) => Promise<unknown>;
322
322
  sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
323
+ shouldContinue?: () => boolean;
324
+ onStopped?: () => void;
323
325
  }
324
326
 
325
327
  export interface TelegramRuntimeEventRecorderPort {
@@ -365,6 +367,8 @@ export interface TelegramTypingLoopStarterDeps<
365
367
  sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
366
368
  updateStatus: (ctx: TContext, error?: string) => void;
367
369
  isContextActive?: (ctx: TContext) => boolean;
370
+ isTransportAvailable?: () => boolean;
371
+ getTransportAuthority?: () => string | number | undefined;
368
372
  intervalMs?: number;
369
373
  }
370
374
 
@@ -376,15 +380,33 @@ export function createTelegramTypingLoopStarter<TContext>(
376
380
  options?: { target?: TelegramTypingLoopTarget },
377
381
  ) => void {
378
382
  return (ctx, chatId, options) => {
383
+ const transportAuthority = deps.getTransportAuthority?.();
384
+ const hasTransport = (): boolean =>
385
+ deps.getTransportAuthority
386
+ ? transportAuthority !== undefined &&
387
+ Object.is(deps.getTransportAuthority(), transportAuthority)
388
+ : deps.isTransportAvailable?.() !== false;
389
+ if (!hasTransport()) return;
390
+ let active = true;
379
391
  deps.typing.start({
380
392
  chatId: chatId ?? deps.getDefaultChatId(),
381
393
  target: options?.target,
382
394
  intervalMs: deps.intervalMs ?? TELEGRAM_TYPING_ACTION_INTERVAL_MS,
383
395
  sendTypingAction: async (targetChatId, actionOptions) => {
396
+ if (!active) return;
397
+ if (!hasTransport()) {
398
+ deps.typing.stop();
399
+ return;
400
+ }
384
401
  try {
385
402
  await deps.sendTypingAction(targetChatId, actionOptions);
386
403
  } catch (error) {
387
404
  if (deps.isContextActive?.(ctx) === false) return;
405
+ if (!active) return;
406
+ if (!hasTransport()) {
407
+ deps.typing.stop();
408
+ return;
409
+ }
388
410
  const message =
389
411
  error instanceof Error ? error.message : String(error);
390
412
  updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
@@ -404,10 +426,20 @@ export function createTelegramTypingLoopStarter<TContext>(
404
426
  },
405
427
  sendAggregateTypingAction: deps.sendAggregateTypingAction
406
428
  ? async (targetChatId) => {
429
+ if (!active) return;
430
+ if (!hasTransport()) {
431
+ deps.typing.stop();
432
+ return;
433
+ }
407
434
  try {
408
435
  await deps.sendAggregateTypingAction?.(targetChatId);
409
436
  } catch (error) {
410
437
  if (deps.isContextActive?.(ctx) === false) return;
438
+ if (!active) return;
439
+ if (!hasTransport()) {
440
+ deps.typing.stop();
441
+ return;
442
+ }
411
443
  const message =
412
444
  error instanceof Error ? error.message : String(error);
413
445
  updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
@@ -427,6 +459,10 @@ export function createTelegramTypingLoopStarter<TContext>(
427
459
  }
428
460
  }
429
461
  : undefined,
462
+ shouldContinue: hasTransport,
463
+ onStopped: () => {
464
+ active = false;
465
+ },
430
466
  });
431
467
  };
432
468
  }
@@ -442,7 +478,12 @@ export function startTelegramTypingLoop(
442
478
  ): boolean {
443
479
  if (deps.chatId === undefined || deps.chatId === 0) return false;
444
480
  const previousKey = state.typingLoopKey;
481
+ const previousDeps = state.typingLoopDeps;
445
482
  const nextKey = getTelegramTypingLoopKey(deps);
483
+ if (previousDeps && previousDeps !== deps) {
484
+ previousDeps.onStopped?.();
485
+ state.typingInFlight = undefined;
486
+ }
446
487
  state.typingLoopDeps = deps;
447
488
  state.typingLoopKey = nextKey;
448
489
  const sendTyping = (): void => {
@@ -450,10 +491,14 @@ export function startTelegramTypingLoop(
450
491
  if (
451
492
  !activeDeps ||
452
493
  activeDeps.chatId === undefined ||
453
- activeDeps.chatId === 0 ||
454
- state.typingInFlight
494
+ activeDeps.chatId === 0
455
495
  )
456
496
  return;
497
+ if (activeDeps.shouldContinue?.() === false) {
498
+ stopTelegramTypingLoop(state);
499
+ return;
500
+ }
501
+ if (state.typingInFlight) return;
457
502
  const targetChatId = activeDeps.chatId;
458
503
  const threadParams = getTelegramTypingLoopThreadParams(activeDeps.target);
459
504
  const typing = Promise.resolve()
@@ -486,9 +531,12 @@ export function stopTelegramTypingLoop(
486
531
  ): boolean {
487
532
  if (!state.typingInterval) return false;
488
533
  clearInterval(state.typingInterval);
534
+ const activeDeps = state.typingLoopDeps;
489
535
  state.typingInterval = undefined;
490
536
  state.typingLoopDeps = undefined;
491
537
  state.typingLoopKey = undefined;
538
+ state.typingInFlight = undefined;
539
+ activeDeps?.onStopped?.();
492
540
  return true;
493
541
  }
494
542
 
@@ -571,6 +619,8 @@ export interface TelegramPromptDispatchRuntimeDeps<
571
619
  sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
572
620
  updateStatus: (ctx: TContext, error?: string) => void;
573
621
  isContextActive?: (ctx: TContext) => boolean;
622
+ isTransportAvailable?: () => boolean;
623
+ getTransportAuthority?: () => string | number | undefined;
574
624
  intervalMs?: number;
575
625
  }
576
626
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.36.9",
3
+ "version": "0.36.10",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"