@llblab/pi-telegram 0.52.0 → 0.52.2

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/lib/threads.ts CHANGED
@@ -332,7 +332,7 @@ export interface TelegramTemporaryThreadInput {
332
332
  journalBindingKey: string;
333
333
  updateIds: number[];
334
334
  }
335
- /** A strict journal observation of committed cancellation plus matching private retention. */
335
+ /** Journal-owned donor discard evidence: retained manual cancellation or body-free chooser expiry, never recipient cancellation. */
336
336
  export interface TelegramTemporaryThreadCancellationEvidence {
337
337
  journalBindingKey: string;
338
338
  updateId: number;
@@ -343,7 +343,7 @@ export interface TelegramTemporaryThreadEntry {
343
343
  source: { journalBindingKey: string; updateId: number };
344
344
  /** Append-only known source groups. Missing legacy metadata never proves that the creation source was alone. */
345
345
  inputs?: TelegramTemporaryThreadInput[];
346
- /** Whole known groups with positively observed cancellation; never proof that deletion may be issued. */
346
+ /** Whole known groups with positively observed donor cancellation or expiry; never recipient cancellation or deletion authority. */
347
347
  cancelledInputs?: TelegramTemporaryThreadInput[];
348
348
  /** Whole known groups whose Forward was positively completed by the journal owner; never deletion authority alone. */
349
349
  completedInputs?: TelegramTemporaryThreadInput[];
@@ -415,6 +415,10 @@ export interface TelegramWorkspaceRestore {
415
415
  recordTemporaryThreadInputCancellation(expected: TelegramTemporaryThreadEntry, input: TelegramTemporaryThreadInput,
416
416
  authority: TelegramWorkspaceRestoreAuthority,
417
417
  inspect: (updateId: number) => TelegramTemporaryThreadCancellationEvidence | undefined): TelegramTemporaryThreadEntry | undefined;
418
+ /** Body-free chooser expiry may terminate an uncertain donor Forward/Restore, never accepted recipient work or a bound target. */
419
+ recordTemporaryThreadInputExpiry(expected: TelegramTemporaryThreadEntry, input: TelegramTemporaryThreadInput,
420
+ authority: TelegramWorkspaceRestoreAuthority,
421
+ inspect: (updateId: number) => TelegramTemporaryThreadCancellationEvidence | undefined): TelegramTemporaryThreadEntry | undefined;
418
422
  /** Caller holds profile admission and proves fresh source/protection clearance; publication grants one attempt, never retry. */
419
423
  issueTemporaryThreadCleanup(expected: TelegramTemporaryThreadEntry, authority: TelegramWorkspaceRestoreAuthority):
420
424
  { issued: true; entry: TelegramTemporaryThreadEntry } | undefined;
@@ -423,8 +427,8 @@ export interface TelegramWorkspaceRestore {
423
427
  retireTemporaryThread(expected: TelegramTemporaryThreadEntry, authority: TelegramWorkspaceRestoreAuthority,
424
428
  completed?: TelegramTemporaryThreadInput): TelegramTemporaryThreadEntry | undefined;
425
429
  /** New-world restart: atomically forgets this operator's Restore intents and temporary entries from previous runtime instances.
426
- * Committed bindings stay; nothing is rolled back, replayed or deleted here. */
427
- forgetPreviousWorld(authority: TelegramWorkspaceRestoreAuthority):
430
+ * Caller may preserve exact unbound temporary tokens for clock-bearing sources. Committed bindings stay; nothing is rolled back, replayed or deleted here. */
431
+ forgetPreviousWorld(authority: TelegramWorkspaceRestoreAuthority, preserveTemporaryTokens?: readonly string[]):
428
432
  { operations: TelegramWorkspaceRestoreIntent[]; temporaryThreads: TelegramTemporaryThreadEntry[] } | undefined;
429
433
  }
430
434
  export interface TelegramWorkspaceRestoreOptions {
@@ -3595,6 +3599,40 @@ export function createTelegramTopicTargetStore(
3595
3599
  });
3596
3600
  return recorded && current() ? recorded : undefined;
3597
3601
  },
3602
+ recordTemporaryThreadInputExpiry(expected, inputValue, authority, inspect) {
3603
+ const input = structuredClone(inputValue);
3604
+ if (!isTemporaryThreadInput(input) || typeof inspect !== "function") return undefined;
3605
+ const operator = authority.operatorUserId, executor = structuredClone(authority.executor);
3606
+ const bound = classifyTemporaryTarget(read(), expected).kind === "bound";
3607
+ const current = () => authority.isCurrent() && authority.operatorUserId === operator &&
3608
+ isDeepStrictEqual(authority.executor, executor) && input.updateIds.every(updateId => {
3609
+ const evidence = inspect(updateId);
3610
+ return evidence?.journalBindingKey === input.journalBindingKey && evidence.updateId === updateId &&
3611
+ evidence.operatorAuthorityId === `telegram-owner:${operator}`;
3612
+ }) && authority.isCurrent();
3613
+ return advanceTemporary(expected, { ...authority, isCurrent: current }, (entry, file) => {
3614
+ if (entry.phase !== "created" || !getTelegramTemporaryThreadInputs(entry).some(value => isDeepStrictEqual(value, input)) ||
3615
+ entry.completedInputs?.some(value => isDeepStrictEqual(value, input))) return false;
3616
+ const cancelled = entry.cancelledInputs ?? [];
3617
+ if (cancelled.some(value => isDeepStrictEqual(value, input))) return "unchanged";
3618
+ const operations = file.operations.filter(({ request }) => request.source.journalBindingKey === input.journalBindingKey &&
3619
+ request.source.updateIds.some(id => input.updateIds.includes(id)));
3620
+ if (operations.some(({ request }) => !request.source.updateIds.every(id => input.updateIds.includes(id)))) return false;
3621
+ // Forget only expired donor intent; canonical binding, recipient queue and immutable settlement proofs are untouched.
3622
+ file.operations = file.operations.filter(value => !operations.includes(value));
3623
+ // Once bound, this is no longer a disposable tab. Forget its temporary frame, not the binding or other journal sources.
3624
+ if (bound) {
3625
+ file.temporaryThreads = file.temporaryThreads?.filter(value => value !== entry);
3626
+ if (!file.temporaryThreads?.length) delete file.temporaryThreads;
3627
+ return true;
3628
+ }
3629
+ const forwarded = entry.forwardedInputs?.filter(value => !isDeepStrictEqual(value, input));
3630
+ if (forwarded?.length) entry.forwardedInputs = forwarded;
3631
+ else delete entry.forwardedInputs;
3632
+ entry.cancelledInputs = [...cancelled, input];
3633
+ return true;
3634
+ });
3635
+ },
3598
3636
  recordTemporaryThreadForwardIssued(expected, inputValue, authority) {
3599
3637
  const input = structuredClone(inputValue);
3600
3638
  if (!isTemporaryThreadInput(input)) return undefined;
@@ -3644,7 +3682,7 @@ export function createTelegramTopicTargetStore(
3644
3682
  return entry;
3645
3683
  });
3646
3684
  },
3647
- forgetPreviousWorld(authority) {
3685
+ forgetPreviousWorld(authority, preserveTemporaryTokens = []) {
3648
3686
  if (!authority.isCurrent()) return undefined;
3649
3687
  const executor = structuredClone(authority.executor), operator = authority.operatorUserId;
3650
3688
  if (!isTelegramWorkspaceRestoreExecutor(executor) || !restoreInteger(operator)) return undefined;
@@ -3652,11 +3690,12 @@ export function createTelegramTopicTargetStore(
3652
3690
  const previous = (value: { operatorUserId: number; executor: TelegramWorkspaceRestoreExecutor }) =>
3653
3691
  value.operatorUserId === operator && value.executor.instanceId !== executor.instanceId;
3654
3692
  const before = storage.read(), file = structuredClone(before);
3655
- const operations = file.operations.filter(previous), temporaryThreads = (file.temporaryThreads ?? []).filter(previous);
3693
+ const forgetTemporary = (value: TelegramTemporaryThreadEntry) => previous(value) && !preserveTemporaryTokens.includes(value.token);
3694
+ const operations = file.operations.filter(previous), temporaryThreads = (file.temporaryThreads ?? []).filter(forgetTemporary);
3656
3695
  if (!current()) return undefined;
3657
3696
  if (!operations.length && !temporaryThreads.length) return { operations, temporaryThreads };
3658
3697
  file.operations = file.operations.filter(value => !previous(value));
3659
- const remaining = (file.temporaryThreads ?? []).filter(value => !previous(value));
3698
+ const remaining = (file.temporaryThreads ?? []).filter(value => !forgetTemporary(value));
3660
3699
  if (remaining.length) file.temporaryThreads = remaining;
3661
3700
  else delete file.temporaryThreads;
3662
3701
  if (file.revision === Number.MAX_SAFE_INTEGER) throw new Error("Workspace Restore revision exhausted.");
package/lib/updates.ts CHANGED
@@ -56,6 +56,7 @@ import {
56
56
  type TelegramUpdateJournalEntry,
57
57
  type TelegramUpdateJournalRoutingInput,
58
58
  type TelegramRoutingInputJournal,
59
+ type TelegramRoutingInputExpiryResult,
59
60
  type TelegramUpdateJournalQueueDiscardResult,
60
61
  type TelegramUpdateJournalQueueHandoffAcceptResult,
61
62
  type TelegramUpdateJournalQueueHandoffCancelResult,
@@ -495,6 +496,8 @@ export interface TelegramHeldSourcePreparation<TContext> {
495
496
  /** Exact prepared worker owner, context, key and authority; the consumer adds its own domain fences. */
496
497
  isCurrent: () => boolean;
497
498
  signal: AbortSignal;
499
+ /** Chooser-clock sources that must survive cold spending until their fixed expiry. */
500
+ routingSourceIds?: readonly number[];
498
501
  }
499
502
 
500
503
  export interface TelegramDeferredAbandonmentRecoveryPage {
@@ -1922,7 +1925,7 @@ export interface TelegramRoutingInputExpirySource {
1922
1925
  original: TelegramUpdateJournalEntry;
1923
1926
  journalBindingKey: string;
1924
1927
  isCurrent(): boolean;
1925
- expire(): TelegramUpdateJournalPendingAbandonmentResult | undefined;
1928
+ expire(): TelegramRoutingInputExpiryResult | undefined;
1926
1929
  }
1927
1930
 
1928
1931
  export interface TelegramUpdateWorkerJournalPort {
@@ -2035,7 +2038,7 @@ export interface TelegramUpdateWorkerRuntimeDeps<TContext> {
2035
2038
  getRecipientBindingKey?: () => string | undefined;
2036
2039
  /** True selects legacy historical review/spending; retain protects unsupported originals without disposition authority. */
2037
2040
  shouldReviewHistoricalInput?: (entry: TelegramUpdateJournalEntry, ctx: TContext, signal: AbortSignal) => boolean | "retain" | Promise<boolean | "retain">;
2038
- /** New-world restart: spend previous-process routing inputs (classified or armed) without delivery, copy or completion observers. Interrupted private abandonment keeps its own exact recovery. */
2041
+ /** New-world restart: spend unclocked classified routing input without delivery, copy or completion. Chooser clocks wait for expiry; interrupted private abandonment keeps exact recovery. */
2039
2042
  spendHistoricalInput?: boolean;
2040
2043
  /** Holds protected live or retry sources before execution; never cancels or disposes them. */
2041
2044
  shouldHoldPendingInput?: (entry: TelegramUpdateJournalEntry, ctx: TContext, signal: AbortSignal) => boolean | Promise<boolean>;
@@ -2090,6 +2093,7 @@ export interface TelegramUpdateWorkerRuntime<TContext> {
2090
2093
  }) => void;
2091
2094
  armRoutingInput?: (input: { updateId: number; signal: AbortSignal; operatorUserId: number; sourceUpdateIds: readonly number[] }) => TelegramUpdateJournalRoutingInput | undefined;
2092
2095
  selectRoutingInput?: (input: { updateId: number; signal: AbortSignal; operatorUserId: number; sourceUpdateIds: readonly number[] }) => boolean;
2096
+ isRoutingInputCurrent?: (input: { updateId: number; signal: AbortSignal }) => boolean;
2093
2097
  supportsDeferredAbandonment?: (input: { updateId: number; signal: AbortSignal; journalBindingKey: string }) => boolean;
2094
2098
  inspectAbandoning?: (input: TelegramDeferredAbandonmentRecoveryRequest & { signal: AbortSignal }) =>
2095
2099
  TelegramDeferredAbandonmentRecoveryPage | undefined;
@@ -3215,18 +3219,29 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
3215
3219
  if (!isTelegramUpdateJournalLegacyFamilyVersion(snapshot.version) || !expireRouting || !expireRoutingObserver) return false;
3216
3220
  let nextAtMs: number | undefined, changed = false;
3217
3221
  const binding = deps.getJournalBindingKey?.(), snapshotIds = new Set(snapshot.entries.map(entry => entry.updateId));
3218
- // Lost removal ACKs reconcile only the exact held original through the retained copy/tombstone.
3222
+ // Lost expiry ACKs reconcile the exact held original against its body-free discard tombstone.
3219
3223
  const candidates = [...snapshot.entries, ...[...deferredSources.values()]
3220
3224
  .filter(source => source.journalBindingKey === binding && !snapshotIds.has(source.entry.updateId))
3221
3225
  .map(source => source.entry)];
3222
3226
  for (const entry of candidates) {
3223
3227
  const lifetime = entry.routingInput;
3224
- if (claims.get(entry.updateId) === "retained" || (expectedOwner.startupUpdateIds?.has(entry.updateId) &&
3225
- !claims.has(entry.updateId) && deps.shouldReviewHistoricalInput)) continue;
3226
- if (!lifetime || lifetime.phase !== "waiting" || entry.state !== "pending" || !binding || unsettledExecutionsByUpdateId.get(entry.updateId)?.size) continue;
3228
+ if (!lifetime || entry.state !== "pending" || !binding || claims.get(entry.updateId) === "queued" ||
3229
+ unsettledExecutionsByUpdateId.get(entry.updateId)?.size) continue;
3230
+ // Classify the first startup snapshot before constructing any historical expiry carrier.
3231
+ if (expectedOwner.startupUpdateIds?.has(entry.updateId) && !claims.has(entry.updateId) && deps.shouldReviewHistoricalInput) {
3232
+ nextAtMs = Math.min(nextAtMs ?? Infinity, Math.max(getNowMs() + 1, lifetime.expiresAtMs));
3233
+ continue;
3234
+ }
3227
3235
  if (getNowMs() < lifetime.expiresAtMs) { nextAtMs = Math.min(nextAtMs ?? Infinity, lifetime.expiresAtMs); continue; }
3228
3236
  const existing = deferredSources.get(entry.updateId);
3229
- if (existing && !isDeepStrictEqual(existing.entry, entry)) continue;
3237
+ if (existing && !isDeepStrictEqual(existing.entry, entry)) {
3238
+ // Lost clock/selection ACKs may leave only this immutable metadata behind its exact source; reconciliation grants expiry, never another dispatch.
3239
+ const previous = existing.entry.routingInput;
3240
+ if (!isDeepStrictEqual({ ...existing.entry, routingInput: lifetime }, entry) ||
3241
+ (previous && (!isDeepStrictEqual({ ...previous, phase: lifetime.phase }, lifetime) ||
3242
+ (previous.phase === "selected" && lifetime.phase !== "selected")))) continue;
3243
+ existing.entry = structuredClone(entry);
3244
+ }
3230
3245
  const source = existing ?? { entry: structuredClone(entry), journalBindingKey: binding, historical: true as const };
3231
3246
  deferredSources.set(entry.updateId, source);
3232
3247
  if (!claims.has(entry.updateId)) claims.set(entry.updateId, "historical");
@@ -3234,7 +3249,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
3234
3249
  deps.isContextCurrent?.(expectedOwner.ctx) !== false && deps.getJournalBindingKey?.() === binding &&
3235
3250
  deps.hasAuthority(expectedOwner.ctx) && isQueueOwnerIdentityCurrent(expectedOwner) && deferredSources.get(entry.updateId) === source &&
3236
3251
  isDeepStrictEqual(source.entry, entry) && claims.get(entry.updateId) !== "queued";
3237
- let committed: TelegramUpdateJournalPendingAbandonmentResult | undefined;
3252
+ let committed: TelegramRoutingInputExpiryResult | undefined;
3238
3253
  try {
3239
3254
  if (current()) await expireRoutingObserver({ original: structuredClone(entry), journalBindingKey: binding, isCurrent: current,
3240
3255
  expire() {
@@ -3242,9 +3257,10 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
3242
3257
  if (committed) return { ...committed, duplicate: true };
3243
3258
  claims.set(entry.updateId, "abandoning");
3244
3259
  committed = expireRouting!({ entry: source.entry, journalBindingKey: binding, operatorUserId: lifetime.operatorUserId, isCurrent: current });
3260
+ // Source expiry is terminal before chooser/cleanup awaits; later failures must not retain a deferred/retry source.
3245
3261
  claims.delete(entry.updateId); deferredSources.delete(entry.updateId);
3246
3262
  state.journalEntryCount = committed.entryCount; state.journalSerializedBytes = committed.serializedBytes;
3247
- updateClaimCounts(); notifyStateChange(); changed = true;
3263
+ updateClaimCounts(); notifyStateChange(); changed ||= !committed.duplicate;
3248
3264
  return committed;
3249
3265
  } }, expectedOwner.ctx, expectedOwner.controller.signal);
3250
3266
  } catch (error) { recordRuntimeEvent(error, { phase: "routing-input-expiry", updateId: entry.updateId }); }
@@ -3445,7 +3461,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
3445
3461
  transition("deferred", entry.updateId);
3446
3462
  continue;
3447
3463
  }
3448
- if (review && historical && spendHistorical && classifyPending === deps.shouldReviewHistoricalInput) {
3464
+ if (review && historical && spendHistorical && !entry.routingInput && classifyPending === deps.shouldReviewHistoricalInput) {
3449
3465
  completedUpdateIds.push({ updateId: entry.updateId, spent: true });
3450
3466
  continue;
3451
3467
  }
@@ -3456,10 +3472,6 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
3456
3472
  }
3457
3473
  }
3458
3474
  if (entry.state === "pending" && entry.routingInput) {
3459
- if (historical && spendHistorical) {
3460
- completedUpdateIds.push({ updateId: entry.updateId, spent: true });
3461
- continue;
3462
- }
3463
3475
  claims.set(entry.updateId, "historical");
3464
3476
  transition("deferred", entry.updateId);
3465
3477
  continue;
@@ -3628,7 +3640,9 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
3628
3640
  expectedOwner.heldSourcesPreparedIssued = true;
3629
3641
  // Do not couple lifecycle/stop or the worker drain to a held controller/API reply.
3630
3642
  void Promise.resolve().then(() => isCurrent() ? onHeldSourcesPrepared({ ctx,
3631
- journalBindingKey: key, signal: expectedOwner.controller.signal, isCurrent }) : undefined).catch(error => {
3643
+ journalBindingKey: key, signal: expectedOwner.controller.signal, isCurrent,
3644
+ routingSourceIds: [...deferredSources].filter(([, source]) => source.journalBindingKey === key && !!source.entry.routingInput)
3645
+ .map(([id]) => id) }) : undefined).catch(error => {
3632
3646
  try { deps.recordRuntimeEvent?.("inbound-worker", error, { phase: "held-source-preparation" }); } catch { /* Diagnostic only. */ }
3633
3647
  });
3634
3648
  } catch (error) {
@@ -3784,6 +3798,10 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
3784
3798
  },
3785
3799
  armRoutingInput(input) { return mutateRoutingSource(input, false)?.lifetime; },
3786
3800
  selectRoutingInput(input) { return mutateRoutingSource(input, true)?.issued === true; },
3801
+ isRoutingInputCurrent(input) {
3802
+ return owner?.controller.signal === input.signal && !input.signal.aborted &&
3803
+ deferredSources.has(input.updateId) && claims.get(input.updateId) !== "abandoning";
3804
+ },
3787
3805
  inspectDeferredSource(input) {
3788
3806
  const expectedOwner = owner;
3789
3807
  const source = deferredSources.get(input.updateId);
@@ -3843,7 +3861,8 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
3843
3861
  return;
3844
3862
  }
3845
3863
  const claim = claims.get(input.updateId);
3846
- if (claim === "abandoning" || claim === "historical" || claim === "retained") return;
3864
+ if (claim === "abandoning" || claim === "historical" || claim === "retained" ||
3865
+ (!claim && routingJournal?.inspectExpiry(input.updateId))) return;
3847
3866
  if (input.outcome.kind === "complete") {
3848
3867
  // Expiry may retire only a still-deferred source, never accepted queue work.
3849
3868
  if (claim !== "deferred") return;
@@ -4453,6 +4472,7 @@ export interface TelegramUpdateAdmissionHandleDeps<
4453
4472
  abandonDeferred?: TelegramUpdateWorkerRuntime<TContext>["abandonDeferred"];
4454
4473
  armRoutingInput?: TelegramUpdateWorkerRuntime<TContext>["armRoutingInput"];
4455
4474
  selectRoutingInput?: TelegramUpdateWorkerRuntime<TContext>["selectRoutingInput"];
4475
+ isRoutingInputCurrent?: TelegramUpdateWorkerRuntime<TContext>["isRoutingInputCurrent"];
4456
4476
  supportsDeferredAbandonment?: TelegramUpdateWorkerRuntime<TContext>["supportsDeferredAbandonment"];
4457
4477
  inspectAbandoning?: TelegramUpdateWorkerRuntime<TContext>["inspectAbandoning"];
4458
4478
  inspectHistorical?: TelegramUpdateWorkerRuntime<TContext>["inspectHistorical"];
@@ -5483,9 +5503,10 @@ export function createTelegramUpdateAdmissionHandle<
5483
5503
  generation,
5484
5504
  updateId: update.update_id,
5485
5505
  signal,
5486
- isCurrent: () => !signal.aborted && !suspended,
5506
+ isCurrent: () => !signal.aborted && !suspended && ((!routingLifetime && !routingClockUnknown) || immediate || settlementReported ||
5507
+ deps.isRoutingInputCurrent?.({ updateId: update.update_id, signal }) !== false),
5487
5508
  assertCurrent() {
5488
- if (signal.aborted || suspended) {
5509
+ if (!execution.isCurrent()) {
5489
5510
  throw signal.reason ?? new DOMException("Aborted", "AbortError");
5490
5511
  }
5491
5512
  },
@@ -5499,7 +5520,7 @@ export function createTelegramUpdateAdmissionHandle<
5499
5520
  let outcome: TelegramUpdateAdmissionOutcome | undefined;
5500
5521
  const boundUpdate = bindTelegramUpdateExecutionFence(
5501
5522
  bindTelegramUpdateAdmissionSource(update, (next) => {
5502
- if (suspended || signal.aborted) return;
5523
+ if (!execution.isCurrent()) return;
5503
5524
  if (next.kind === "deferred" && next.routingReview) {
5504
5525
  if (!immediate || settlementReported || routingClaims > 0 || !deps.isHistoricalSource?.({ updateId: update.update_id, signal })) {
5505
5526
  throw new TelegramUpdateAdmissionOutcomeError("Historical review cannot override accepted or selected work.");
@@ -5522,7 +5543,7 @@ export function createTelegramUpdateAdmissionHandle<
5522
5543
  }
5523
5544
  void Promise.resolve()
5524
5545
  .then(() => {
5525
- if (suspended || signal.aborted) return;
5546
+ if (!execution.isCurrent()) return;
5526
5547
  return deps.onLateOutcome!(next, {
5527
5548
  updateId: update.update_id,
5528
5549
  ctx,
@@ -5605,7 +5626,7 @@ export function createTelegramCustodiedUpdateAdmissionHandle<
5605
5626
  TUpdate extends TelegramJournaledUpdate & TelegramUpdateFlow,
5606
5627
  TContext,
5607
5628
  >(deps: Omit<TelegramUpdateAdmissionHandleDeps<TUpdate, TContext>,
5608
- "onLateOutcome" | "onLateOutcomeError" | "abandonDeferred" | "armRoutingInput" | "selectRoutingInput" | "supportsDeferredAbandonment" | "inspectAbandoning" | "inspectHistorical" | "inspectDeferredSource" | "isHistoricalSource"> & {
5629
+ "onLateOutcome" | "onLateOutcomeError" | "abandonDeferred" | "armRoutingInput" | "selectRoutingInput" | "isRoutingInputCurrent" | "supportsDeferredAbandonment" | "inspectAbandoning" | "inspectHistorical" | "inspectDeferredSource" | "isHistoricalSource"> & {
5609
5630
  journal: TelegramCustodyExecutionJournal;
5610
5631
  recipientBindingKey: string;
5611
5632
  onLateOutcomeError(error: unknown, updateId: number): void;
@@ -6983,6 +7004,7 @@ export function createTelegramUpdateAdmissionWorkerRuntime<
6983
7004
  abandonDeferred: input => worker?.abandonDeferred?.(input),
6984
7005
  armRoutingInput: input => worker?.armRoutingInput?.(input),
6985
7006
  selectRoutingInput: input => worker?.selectRoutingInput?.(input) === true,
7007
+ isRoutingInputCurrent: input => worker?.isRoutingInputCurrent?.(input) !== false,
6986
7008
  supportsDeferredAbandonment: input => worker?.supportsDeferredAbandonment?.(input) === true,
6987
7009
  inspectAbandoning: input => worker?.inspectAbandoning?.(input),
6988
7010
  inspectHistorical: input => worker?.inspectHistorical?.(input),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.52.0",
3
+ "version": "0.52.2",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"