@llblab/pi-telegram 0.56.0 → 0.56.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
@@ -783,6 +783,7 @@ export interface TelegramTopicTargetStore {
783
783
  ) => { slot?: string; threadName?: string } | undefined;
784
784
  listReservations: () => TelegramThreadReservation[];
785
785
  listPendingProvisions: () => TelegramThreadPendingProvision[];
786
+ resetPendingProvisionForConnect: (expected: TelegramThreadPendingProvision, isCurrent: () => boolean) => Promise<boolean>;
786
787
  listPendingCleanups: () => TelegramThreadCleanupIntent[];
787
788
  listSyncObservations: () => TelegramTopicSyncObservation[];
788
789
  reserveThread: (reservation: TelegramThreadReservation) => void;
@@ -902,6 +903,7 @@ export interface TelegramTopicTargetStore {
902
903
  options?: {
903
904
  existingBindingOnly?: boolean;
904
905
  sessionId?: string;
906
+ provisionProfileKey?: string;
905
907
  onCapacityUnavailable?: () => void;
906
908
  },
907
909
  ) => TelegramWorkspaceBindingIdentity | undefined;
@@ -4168,6 +4170,7 @@ export function createTelegramTopicTargetStore(
4168
4170
  }
4169
4171
  ),
4170
4172
  isCurrentPublication?: () => boolean,
4173
+ resetProvisions?: readonly TelegramThreadPendingProvision[],
4171
4174
  ): Promise<boolean> => {
4172
4175
  const submittedPath = getPath(),
4173
4176
  submittedProfile = activeProfile();
@@ -4367,6 +4370,13 @@ export function createTelegramTopicTargetStore(
4367
4370
  !!expectedRestore,
4368
4371
  ),
4369
4372
  );
4373
+ if (resetProvisions) {
4374
+ const recoveries = readProvisionRecoveries(path, true);
4375
+ if (mutationRevision !== persistedRevision || resetProvisions.some(expected =>
4376
+ expected.target || Object.hasOwn(recoveries, expected.id) ||
4377
+ !isDeepStrictEqual(disk.pendingProvisions?.find(value => value.id === expected.id), expected)))
4378
+ throw new Error("Telegram connection recovery provisioning evidence changed.");
4379
+ }
4370
4380
  if (!isDeepStrictEqual(disk.workspaceRestore, expectedRestore))
4371
4381
  throw new Error(
4372
4382
  "Workspace Restore evidence changed before publication.",
@@ -4429,7 +4439,7 @@ export function createTelegramTopicTargetStore(
4429
4439
  }
4430
4440
  : {}),
4431
4441
  reservations: reservations.map((reservation) => ({ ...reservation })),
4432
- pendingProvisions: pendingProvisions.map((provision) => ({
4442
+ pendingProvisions: pendingProvisions.filter(provision => !resetProvisions?.some(expected => expected.id === provision.id)).map((provision) => ({
4433
4443
  ...provision,
4434
4444
  ...(provision.target ? { target: { ...provision.target } } : {}),
4435
4445
  })),
@@ -4636,6 +4646,7 @@ export function createTelegramTopicTargetStore(
4636
4646
  loadedProfile = submittedProfile;
4637
4647
  loaded = true;
4638
4648
  observedWorkspaceSemantic = getTelegramStateSemanticSnapshot(wire);
4649
+ if (resetProvisions) { pendingProvisions = file.pendingProvisions; mutationRevision += 1; }
4639
4650
  if (transition) {
4640
4651
  records = new Map(
4641
4652
  file.threads.map((record) => {
@@ -4808,6 +4819,10 @@ export function createTelegramTopicTargetStore(
4808
4819
  if (isCurrentPublication?.() === false) return false;
4809
4820
  loadedPath = path;
4810
4821
  loaded = true;
4822
+ if (resetProvisions) {
4823
+ if (mutationRevision !== persistedRevision) return false;
4824
+ pendingProvisions = file.pendingProvisions;
4825
+ }
4811
4826
  if (mutationRevision === persistedRevision) dirty = false;
4812
4827
  return true;
4813
4828
  });
@@ -6814,6 +6829,11 @@ export function createTelegramTopicTargetStore(
6814
6829
  current.status = "ambiguous";
6815
6830
  return true;
6816
6831
  },
6832
+ resetPendingProvisionForConnect(expected, isCurrent) {
6833
+ // Stage no in-memory deletion: refusal or a stale publication must keep its protective hold.
6834
+ if (expected.target || !isCurrent()) return Promise.resolve(false);
6835
+ return persistSnapshot(undefined, isCurrent, [structuredClone(expected)]);
6836
+ },
6817
6837
  removePendingProvision(id) {
6818
6838
  const before = pendingProvisions.length;
6819
6839
  pendingProvisions = pendingProvisions.filter(
@@ -7608,8 +7628,14 @@ export function createTelegramTopicTargetStore(
7608
7628
  ) {
7609
7629
  return undefined;
7610
7630
  }
7611
- const retainedTarget = binding?.target ?? legacyRecord?.target;
7612
- const retainedSlot = binding?.slot ?? legacyRecord?.slot;
7631
+ const ownedProvisions = pendingProvisions.filter(value =>
7632
+ value.workspaceBindingKey === identity.bindingKey &&
7633
+ (value.instanceId === instanceId || value.instanceId === effectivePreviousInstanceId ||
7634
+ (options?.provisionProfileKey !== undefined && value.profileKey === options.provisionProfileKey)));
7635
+ if (ownedProvisions.length > 1) return undefined;
7636
+ const ownProvision = ownedProvisions[0];
7637
+ const retainedTarget = ownProvision?.target ?? binding?.target ?? legacyRecord?.target;
7638
+ const retainedSlot = ownProvision?.slot ?? binding?.slot ?? legacyRecord?.slot;
7613
7639
  const otherBindings = Array.from(workspaceBindings.values())
7614
7640
  .filter(
7615
7641
  (other) => other.bindingKey !== identity.bindingKey && other.slot,
@@ -7641,7 +7667,7 @@ export function createTelegramTopicTargetStore(
7641
7667
  )
7642
7668
  .map((reservation) => reservation.slot),
7643
7669
  ...pendingProvisions
7644
- .filter((provision) =>
7670
+ .filter((provision) => provision !== ownProvision &&
7645
7671
  isPendingProvisionLiveOrTargeted(provision, nowMs),
7646
7672
  )
7647
7673
  .map((provision) => provision.slot),
@@ -9661,6 +9687,32 @@ export function createTelegramTopicTargetRenamer(
9661
9687
  };
9662
9688
  }
9663
9689
 
9690
+ /** Caller holds profile Workspace admission; only an explicit connect grants a fresh creation attempt. */
9691
+ export async function resetTelegramConnectionProvisioning(input: {
9692
+ store: TelegramTopicTargetStore;
9693
+ owner: TelegramThreadPendingProvision["owner"];
9694
+ instanceId: string;
9695
+ profileKey: string;
9696
+ isCurrent: () => boolean;
9697
+ recordEvent: (category: string, message: unknown, details?: Record<string, unknown>) => void;
9698
+ }): Promise<void> {
9699
+ if (!input.isCurrent()) throw new Error("Telegram connection recovery authority changed.");
9700
+ const removed: TelegramThreadPendingProvision[] = [];
9701
+ for (const pending of input.store.listPendingProvisions()) {
9702
+ if (pending.owner !== input.owner || pending.target ||
9703
+ (pending.profileKey !== input.profileKey && pending.instanceId !== input.instanceId)) continue;
9704
+ if (!input.isCurrent()) throw new Error("Telegram connection recovery authority changed.");
9705
+ if (!await input.store.resetPendingProvisionForConnect(pending, input.isCurrent))
9706
+ throw new Error("Telegram connection recovery lost publication authority.");
9707
+ if (!input.isCurrent()) throw new Error("Telegram connection recovery authority changed.");
9708
+ removed.push(pending);
9709
+ }
9710
+ if (removed.length === 0) return;
9711
+ input.recordEvent("connection", "Explicit connect reset unfinished Thread creation; an unconfirmed Telegram Thread may remain.", {
9712
+ phase: "connect-provision-reset", count: removed.length, owner: input.owner,
9713
+ });
9714
+ }
9715
+
9664
9716
  export function createTelegramTopicTargetProvisioner(
9665
9717
  deps: TelegramTopicTargetProvisionerDeps,
9666
9718
  ): (
@@ -9923,7 +9975,7 @@ export function createTelegramTopicTargetProvisioner(
9923
9975
  slot,
9924
9976
  );
9925
9977
  }
9926
- const pendingId = `provision:${request.instanceId}:${slot}:${nowMs}`;
9978
+ const pendingId = `provision:${request.instanceId}:${slot}:${nowMs}:${randomUUID()}`;
9927
9979
  const pendingOwner =
9928
9980
  request.owner?.kind === "leader" ? "leader" : "manual-follower";
9929
9981
  assertLeaderEpoch("before-pending-intent");
package/lib/updates.ts CHANGED
@@ -108,16 +108,10 @@ export type TelegramReactionType =
108
108
 
109
109
  export const TELEGRAM_PRIORITY_REACTIONS = [
110
110
  { id: 10, name: "like", emoji: "👍" },
111
- { id: 12, name: "heart", emoji: "❤" },
112
- { id: 13, name: "dove", emoji: "🕊" },
113
111
  ] as const;
114
112
  export const TELEGRAM_STEER_REACTIONS = [{ id: 14, name: "fire", emoji: "🔥" }] as const;
115
113
  export const TELEGRAM_REMOVAL_REACTIONS = [
116
114
  { id: 20, name: "dislike", emoji: "👎" },
117
- { id: 21, name: "ghost", emoji: "👻" },
118
- { id: 22, name: "broken-heart", emoji: "💔" },
119
- { id: 23, name: "poop", emoji: "💩" },
120
- { id: 24, name: "wastebasket", emoji: "🗑" },
121
115
  ] as const;
122
116
  export const TELEGRAM_PRIORITY_REACTION_EMOJIS =
123
117
  TELEGRAM_PRIORITY_REACTIONS.map((reaction) => reaction.emoji);
@@ -159,7 +153,7 @@ function getTelegramQueueReactionTransition(
159
153
  ): TelegramQueueReactionDisposition | undefined {
160
154
  const oldEmojis = collectTelegramReactionEmojis(oldReactions);
161
155
  const newEmojis = collectTelegramReactionEmojis(newReactions);
162
- // Fire is effective Steer intent; weaker positive aliases under it do not reorder the lane.
156
+ // Fire selects Steer; thumbs-up beneath it does not reorder the lane. Other emoji carry no queue intent.
163
157
  const steerEmoji = TELEGRAM_STEER_REACTIONS[0].emoji;
164
158
  const oldPriorityEmoji = oldEmojis.has(steerEmoji) ? steerEmoji : getTelegramReactionEmoji(oldEmojis, TELEGRAM_PRIORITY_REACTION_EMOJIS);
165
159
  const newPriorityEmoji = newEmojis.has(steerEmoji) ? steerEmoji : getTelegramReactionEmoji(newEmojis, TELEGRAM_PRIORITY_REACTION_EMOJIS);
@@ -547,6 +541,7 @@ interface TelegramUpdateAdmissionBinding {
547
541
  inspectSource?: () => TelegramDeferredSourceEvidence | undefined;
548
542
  inspectSourceSnapshot?: () => TelegramDeferredSourceSnapshot | undefined;
549
543
  inspectCompletion?: () => TelegramDeferredSourceEvidence | undefined;
544
+ subscribeCompletion?: (notify: () => void) => () => void;
550
545
  isSourceUnsettled?: () => boolean;
551
546
  prepareLiveInput?: (
552
547
  sourceUpdateIds: readonly number[],
@@ -615,6 +610,7 @@ export function bindTelegramUpdateAdmissionSource<
615
610
  | "inspectSource"
616
611
  | "inspectSourceSnapshot"
617
612
  | "inspectCompletion"
613
+ | "subscribeCompletion"
618
614
  | "isSourceUnsettled"
619
615
  | "acknowledgeTransferred"
620
616
  | "prepareLiveInput"
@@ -743,6 +739,15 @@ export function inspectTelegramDeferredSourceCompletion(
743
739
  return execution.isCurrent() && source ? { ...source } : undefined;
744
740
  }
745
741
 
742
+ /** Warm ACK notification only; subscribers must independently recheck exact completion and recipient authority. */
743
+ export function subscribeTelegramDeferredSourceCompletion(value: unknown, notify: () => void): (() => void) | undefined {
744
+ const execution = getTelegramUpdateExecutionFence(value);
745
+ if (execution?.isCurrent() !== true) return undefined;
746
+ return getTelegramUpdateAdmissionBinding(value)?.subscribeCompletion?.(() => {
747
+ if (execution.isCurrent()) notify();
748
+ });
749
+ }
750
+
746
751
  /** Reuse only live bound deferred originals; raw IDs or cold/adopted input cannot mint this carrier. */
747
752
  export function prepareTelegramLiveDeferredInput(
748
753
  values: readonly unknown[],
@@ -8721,6 +8726,7 @@ function createTelegramUpdateSourceAdmissionHandle<
8721
8726
  let outcome: TelegramUpdateAdmissionOutcome | undefined;
8722
8727
  let completionAttempted = false;
8723
8728
  let completedSource: TelegramDeferredSourceCompletion | undefined;
8729
+ const completionObservers = new Set<() => void>();
8724
8730
  const boundUpdate = bindTelegramUpdateExecutionFence(
8725
8731
  bindTelegramUpdateAdmissionSource(
8726
8732
  update,
@@ -8777,11 +8783,12 @@ function createTelegramUpdateSourceAdmissionHandle<
8777
8783
  execution.isCurrent() &&
8778
8784
  completion.isCurrent() &&
8779
8785
  isDeepStrictEqual(completion.source, expectedSource)
8780
- )
8781
- completedSource = {
8782
- ...completion,
8783
- source: { ...expectedSource },
8784
- };
8786
+ ) {
8787
+ completedSource = { ...completion, source: { ...expectedSource } };
8788
+ for (const notify of completionObservers) {
8789
+ try { notify(); } catch { /* Observation cannot invalidate an exact removal ACK. */ }
8790
+ }
8791
+ }
8785
8792
  })
8786
8793
  .catch((error) => {
8787
8794
  try {
@@ -8842,6 +8849,10 @@ function createTelegramUpdateSourceAdmissionHandle<
8842
8849
  signal,
8843
8850
  })
8844
8851
  : undefined,
8852
+ subscribeCompletion: (notify) => {
8853
+ completionObservers.add(notify);
8854
+ return () => { completionObservers.delete(notify); };
8855
+ },
8845
8856
  inspectCompletion: () =>
8846
8857
  execution.isCurrent() && completedSource?.isCurrent()
8847
8858
  ? { ...completedSource.source }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.56.0",
3
+ "version": "0.56.2",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"