@llblab/pi-kit 0.35.0 → 0.35.1

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 (42) hide show
  1. package/BACKLOG.md +1 -0
  2. package/CHANGELOG.md +5 -0
  3. package/README.md +1 -1
  4. package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
  5. package/node_modules/@llblab/pi-telegram/BACKLOG.md +6 -1
  6. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +6 -0
  7. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +3 -2
  8. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +11 -3
  9. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +2 -2
  10. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +2 -0
  11. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +21 -8
  12. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +40 -45
  13. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +7 -0
  14. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +80 -0
  15. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +12 -0
  16. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +33 -3
  17. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +6 -2
  18. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +46 -4
  19. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +5 -1
  20. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +2 -2
  21. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +2 -0
  22. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +137 -13
  23. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +10 -5
  24. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +19 -2
  25. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +23 -11
  26. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  27. package/node_modules/@llblab/pi-telegram/docs/architecture.md +5 -1
  28. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +3 -1
  29. package/node_modules/@llblab/pi-telegram/docs/outbound.md +4 -2
  30. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +13 -2
  31. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +2 -1
  32. package/node_modules/@llblab/pi-telegram/lib/commands.ts +21 -8
  33. package/node_modules/@llblab/pi-telegram/lib/extension.ts +43 -55
  34. package/node_modules/@llblab/pi-telegram/lib/journal.ts +80 -0
  35. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +36 -3
  36. package/node_modules/@llblab/pi-telegram/lib/queue.ts +43 -4
  37. package/node_modules/@llblab/pi-telegram/lib/replies.ts +3 -2
  38. package/node_modules/@llblab/pi-telegram/lib/routing.ts +140 -18
  39. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +22 -5
  40. package/node_modules/@llblab/pi-telegram/lib/threads.ts +25 -15
  41. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  42. package/package.json +2 -2
@@ -1568,6 +1568,13 @@ export interface TelegramInboundRouteRuntimeDeps<
1568
1568
  requiredJournalBindingKeys: readonly string[],
1569
1569
  ownInputs?: readonly Threads.TelegramTemporaryThreadInput[],
1570
1570
  ) => readonly number[] | undefined;
1571
+ /** Explicit cleanup-only recovery; never called by a read-only protection observer. */
1572
+ recoverTemporaryThreadDeadQueueOwners?: (
1573
+ target: Queue.TelegramQueueTarget,
1574
+ requiredJournalBindingKeys: readonly string[],
1575
+ ownInputs: readonly Threads.TelegramTemporaryThreadInput[],
1576
+ isCurrent: () => boolean,
1577
+ ) => number;
1571
1578
  /** Quiet period after the last cancelled input before one cleanup attempt; defaults to 0 ms. */
1572
1579
  temporaryThreadCleanupDelayMs?: number;
1573
1580
  /** Live-rebind cleanup attempt pacing; the bounded window ends in `not-issued`, never a time-based deletion. */
@@ -6837,6 +6844,93 @@ export function createTelegramInboundRouteRuntime<
6837
6844
  }
6838
6845
  await promptEnqueue(messages, ctx);
6839
6846
  };
6847
+ /** Local menu acceptance owns a one-shot effect; source removal and cleanup precede its release, never its replay. */
6848
+ const stageTemporaryRerouteMenu = async (
6849
+ pending: PendingUnboundReroute,
6850
+ command: Commands.ParsedTelegramCommand,
6851
+ messages: TMessage[],
6852
+ ctx: TContext,
6853
+ assertRecipientCurrent: () => void,
6854
+ completion: NonNullable<ReturnType<typeof Updates.prepareTelegramDeferredSourceCompletion>>,
6855
+ ): Promise<(() => Promise<void>) | undefined> => {
6856
+ const original = pending.messages[0];
6857
+ const cap = captureTemporaryThreadAuthority(ctx);
6858
+ const temporary = pending.temporaryThread ?? findCreatedTemporaryThread(pending.sourceTarget);
6859
+ if (!cap?.isCurrent() || !temporary) return undefined;
6860
+ let accept!: (value: boolean) => void, resume!: (value: boolean) => void;
6861
+ const accepted = new Promise<boolean>(resolve => { accept = resolve; });
6862
+ const released = new Promise<boolean>(resolve => { resume = resolve; });
6863
+ const admission: Commands.TelegramSelectedCommandAdmission = {
6864
+ assertSourceCurrent() {
6865
+ if (!completion.isCurrent()) throw new Error("Rerouted menu source authority changed.");
6866
+ },
6867
+ assertRecipientCurrent,
6868
+ reportCompleted() {
6869
+ const result = completion.reportCompleted();
6870
+ accept(result);
6871
+ return result;
6872
+ },
6873
+ async beforeEffects() {
6874
+ if (!(await released)) throw new Error("Rerouted menu execution was not released.");
6875
+ },
6876
+ };
6877
+ const prepare = command.name === "start" ? commandHandler.prepareSelectedStartCommand
6878
+ : command.name === "help" ? commandHandler.prepareSelectedHelpCommand : commandHandler.prepareSelectedMenuCommand;
6879
+ const dispatch = prepare(command, messages, ctx, admission);
6880
+ if (!dispatch) return undefined;
6881
+ const execution = dispatch().catch(error => {
6882
+ deps.recordRuntimeEvent?.("routing", error, { phase: "reroute-menu-execution" });
6883
+ return false;
6884
+ });
6885
+ if (!(await Promise.race([accepted, execution.then(() => false)]))) {
6886
+ resume(false);
6887
+ await execution;
6888
+ return undefined;
6889
+ }
6890
+ return async () => {
6891
+ let allowEffects = false;
6892
+ try {
6893
+ const confirmed = await new Promise<boolean>(resolve => {
6894
+ let done = false, unsubscribe: (() => void) | undefined;
6895
+ const signal = Updates.getTelegramUpdateExecutionFence(original)?.signal;
6896
+ const finish = (result: boolean) => {
6897
+ if (done) return;
6898
+ done = true;
6899
+ clearTimeout(timer);
6900
+ unsubscribe?.();
6901
+ signal?.removeEventListener("abort", abort);
6902
+ resolve(result);
6903
+ };
6904
+ const abort = () => finish(false);
6905
+ const observe = () => {
6906
+ if (!cap.isCurrent() || !completion.isCurrent()) finish(false);
6907
+ else if (completion.inspectCompletion()) finish(true);
6908
+ };
6909
+ const timer = setTimeout(abort, deps.liveRebindCommandCompletionTimeoutMs ?? 8_000);
6910
+ timer.unref?.();
6911
+ unsubscribe = Updates.subscribeTelegramDeferredSourceCompletion(original, observe);
6912
+ signal?.addEventListener("abort", abort, { once: true });
6913
+ if (signal?.aborted) abort(); else observe();
6914
+ });
6915
+ if (!confirmed) throw new Error("Rerouted menu source removal is unconfirmed; execution remains suppressed.");
6916
+ assertRecipientCurrent();
6917
+ cancelTemporaryThreadCleanup(temporary.token);
6918
+ try {
6919
+ await runTemporaryThreadCleanup(temporary.token, cap, false);
6920
+ } catch (error) {
6921
+ // Cleanup uncertainty retains its canonical grant, not command execution authority or a retry license.
6922
+ deps.recordRuntimeEvent?.("routing", error, { phase: "reroute-menu-cleanup" });
6923
+ }
6924
+ assertRecipientCurrent();
6925
+ allowEffects = true;
6926
+ } catch (error) {
6927
+ deps.recordRuntimeEvent?.("routing", error, { phase: "reroute-menu-release" });
6928
+ } finally {
6929
+ resume(allowEffects);
6930
+ await execution;
6931
+ }
6932
+ };
6933
+ };
6840
6934
  const finalizePendingReroute = async (
6841
6935
  rerouteId: string,
6842
6936
  pending: PendingUnboundReroute,
@@ -7258,12 +7352,20 @@ export function createTelegramInboundRouteRuntime<
7258
7352
  });
7259
7353
  if (!cancelledCurrent()) return;
7260
7354
  entry = cap.adopt(entry);
7261
- if (
7262
- !entry ||
7263
- !cap.isCurrent() ||
7264
- isRerouteTargetProtected(target, undefined, entry)
7265
- )
7266
- return;
7355
+ if (!entry || !cap.isCurrent()) return;
7356
+ if (isRerouteTargetProtected(target, undefined, entry) && deps.recoverTemporaryThreadDeadQueueOwners &&
7357
+ store.inspectTemporaryThreadTarget(entry, cap.authority)?.kind === "temporary") {
7358
+ const inputs = Threads.getTelegramTemporaryThreadInputs(entry);
7359
+ const removed = deps.recoverTemporaryThreadDeadQueueOwners(
7360
+ target, [cap.journalBindingKey, ...inputs.map(input => input.journalBindingKey)],
7361
+ inputs, cap.isCurrent,
7362
+ );
7363
+ if (removed > 0) deps.recordRuntimeEvent?.("routing",
7364
+ "Discarded historical queue receipts from confirmed-dead owners before temporary Thread cleanup.",
7365
+ { phase: "temporary-thread-dead-owner-recovery", removedUpdateCount: removed });
7366
+ }
7367
+ // Recovery is not deletion authority: recapture every ordinary protection after the journal CAS.
7368
+ if (!cap.isCurrent() || isRerouteTargetProtected(target, undefined, entry)) return;
7267
7369
  const issued = store.issueTemporaryThreadCleanup(entry, cap.authority);
7268
7370
  if (!issued || !cap.isCurrent()) return;
7269
7371
  entry = issued.entry;
@@ -8658,6 +8760,7 @@ export function createTelegramInboundRouteRuntime<
8658
8760
  const executeUnboundRerouteCallbackOperation = async (
8659
8761
  query: TCallbackQuery,
8660
8762
  ctx: TContext,
8763
+ afterAdmission: (release: () => Promise<void>) => void,
8661
8764
  ): Promise<boolean> => {
8662
8765
  const parsed = parseTelegramUnboundRerouteCallbackData(query.data);
8663
8766
  if (!parsed) return false;
@@ -9349,7 +9452,20 @@ export function createTelegramInboundRouteRuntime<
9349
9452
  const localMessages = pending.rerouteCopies
9350
9453
  ? cloneTelegramMessagesForThread(pending.messages, parsed.threadId, pending.rerouteCopies.bySourceUpdateId)
9351
9454
  : reroutedMessages;
9352
- await dispatchPendingRerouteMessages(pending, localMessages, ctx);
9455
+ const command = pending.dispatchKind === "command"
9456
+ ? getLiveRebindMenu(localMessages) ?? getLiveRebindHelp(localMessages) : undefined;
9457
+ const completion = pending.rerouteCopies && command
9458
+ ? Updates.prepareTelegramDeferredSourceCompletion(pending.messages[0]) : undefined;
9459
+ if (command && completion) {
9460
+ const release = await stageTemporaryRerouteMenu(pending, command, localMessages, ctx, assertRerouteCurrent, completion);
9461
+ if (!release) {
9462
+ await deps.answerCallbackQuery(query.id, "Command acceptance unconfirmed; not resending");
9463
+ return true;
9464
+ }
9465
+ afterAdmission(release);
9466
+ } else {
9467
+ await dispatchPendingRerouteMessages(pending, localMessages, ctx);
9468
+ }
9353
9469
  assertRerouteCurrent();
9354
9470
  pending.messages = [];
9355
9471
  return completeRoutedReroute();
@@ -9360,17 +9476,23 @@ export function createTelegramInboundRouteRuntime<
9360
9476
  ): Promise<boolean> => {
9361
9477
  const parsed = parseTelegramUnboundRerouteCallbackData(query.data);
9362
9478
  if (!parsed) return false;
9363
- return withPendingRerouteSelection(parsed.rerouteId, query, () => {
9364
- if (!deps.runWorkspaceOperation)
9365
- return executeUnboundRerouteCallbackOperation(query, ctx);
9366
- return deps.runWorkspaceOperation(
9367
- {
9368
- operationId: `workspace-reroute:${query.id}`,
9369
- operationKind: "workspace.route-unbound-thread",
9370
- scopes: [{ kind: "profile" }],
9371
- },
9372
- () => executeUnboundRerouteCallbackOperation(query, ctx),
9373
- );
9479
+ return withPendingRerouteSelection(parsed.rerouteId, query, async () => {
9480
+ let release: (() => Promise<void>) | undefined;
9481
+ const operation = () => executeUnboundRerouteCallbackOperation(query, ctx, value => { release = value; });
9482
+ try {
9483
+ if (!deps.runWorkspaceOperation) return await operation();
9484
+ return await deps.runWorkspaceOperation(
9485
+ {
9486
+ operationId: `workspace-reroute:${query.id}`,
9487
+ operationKind: "workspace.route-unbound-thread",
9488
+ scopes: [{ kind: "profile" }],
9489
+ },
9490
+ operation,
9491
+ );
9492
+ } finally {
9493
+ // Keep the exact source references, but release the non-reentrant Workspace gate before cleanup.
9494
+ await release?.();
9495
+ }
9374
9496
  });
9375
9497
  };
9376
9498
  const handleUnboundRerouteCallback = async (
@@ -473,6 +473,8 @@ export interface TelegramAnswerGuestQueryOptions {
473
473
  }
474
474
 
475
475
  export interface TelegramEditGuestInlineMessageContent {
476
+ /** Cached media only: inline edits cannot upload a new local file. */
477
+ media?: { type: "photo" | "document" | "audio"; media: string; caption?: string };
476
478
  text?: string;
477
479
  richMessage?: TelegramInputRichMessage;
478
480
  parseMode?: "HTML";
@@ -917,10 +919,9 @@ export interface TelegramBridgeApiRuntime extends Pick<
917
919
  options?: TelegramAnswerGuestQueryOptions,
918
920
  ) => Promise<void>;
919
921
  /**
920
- * Temporary Guest Mode ACK experiment: answers the guest query and returns
921
- * the sent inline message id so the final answer can edit that early reply.
922
- * Requires direct transport ownership; Telegram does not document editing
923
- * guest answers, so this exists only to falsify that behavior live.
922
+ * Answers the guest query and retains the sent inline message id for final
923
+ * text or cached-media replacement. Requires direct transport ownership;
924
+ * actual guest-media rendering remains live-client gated.
924
925
  */
925
926
  answerGuestQueryForInlineMessage: (
926
927
  guestQueryId: string,
@@ -930,6 +931,7 @@ export interface TelegramBridgeApiRuntime extends Pick<
930
931
  editGuestInlineMessage: (
931
932
  inlineMessageId: string,
932
933
  content: TelegramEditGuestInlineMessageContent,
934
+ options?: Pick<TelegramApiCallOptions, "assertAuthority">,
933
935
  ) => Promise<void>;
934
936
  deleteMessage: (chatId: number, messageId: number) => Promise<void>;
935
937
  prepareTempDir: () => Promise<number>;
@@ -2404,14 +2406,29 @@ export function createTelegramBridgeApiRuntime(
2404
2406
  editGuestInlineMessage: async (
2405
2407
  inlineMessageId: string,
2406
2408
  content: TelegramEditGuestInlineMessageContent,
2409
+ options?: Pick<TelegramApiCallOptions, "assertAuthority">,
2407
2410
  ) => {
2411
+ if (content.media) {
2412
+ try {
2413
+ const result = await callRecorded("editMessageMedia", {
2414
+ inline_message_id: inlineMessageId,
2415
+ media: content.media,
2416
+ }, { ...options, maxAttempts: 1, retryRateLimit: false });
2417
+ if (result !== true) throw new Error("Missing inline edit acknowledgement.");
2418
+ } catch (error) {
2419
+ // Only a parsed rejection permits replacing this attempt with a failure notice.
2420
+ if (isTelegramApiRequestRejected(error, "editMessageMedia")) throw error;
2421
+ throw new TelegramApiCommitUnknownError("editMessageMedia", error);
2422
+ }
2423
+ return;
2424
+ }
2408
2425
  await callRecorded("editMessageText", {
2409
2426
  inline_message_id: inlineMessageId,
2410
2427
  ...(content.richMessage
2411
2428
  ? { rich_message: content.richMessage }
2412
2429
  : { text: content.text }),
2413
2430
  ...(content.parseMode ? { parse_mode: content.parseMode } : {}),
2414
- });
2431
+ }, options);
2415
2432
  },
2416
2433
  prepareTempDir: () =>
2417
2434
  prepareTelegramTempDir(deps.tempDir, deps.tempFileMaxAgeMs),
@@ -2723,10 +2723,16 @@ function liveRebindOldTargetUnknown(value: TelegramWorkspaceLiveRebindIntent): b
2723
2723
  function retainLiveRebind(value: TelegramWorkspaceLiveRebindIntent): boolean {
2724
2724
  return liveRebindActive(value) || liveRebindOldTargetUnknown(value) || value.oldTargetProtected === true;
2725
2725
  }
2726
- function protectedLiveRebindOldTargets(snapshot: TelegramWorkspaceRestoreSnapshot | undefined): TelegramTarget[] {
2726
+ function provisioningProtectionIntents(snapshot: TelegramWorkspaceRestoreSnapshot): readonly Pick<TelegramWorkspaceRestoreIntent, "request">[] {
2727
+ return [...snapshot.operations, ...(snapshot.liveRebindings ?? []).filter(retainLiveRebind)];
2728
+ }
2729
+ function protectedLiveRebindOldTargetIntents(snapshot: TelegramWorkspaceRestoreSnapshot | undefined): TelegramWorkspaceLiveRebindIntent[] {
2727
2730
  return (snapshot?.liveRebindings ?? []).filter(value =>
2728
2731
  value.oldTargetProtected === true || liveRebindOldTargetUnknown(value) || value.inputRelease !== undefined && value.cleanup === "issued",
2729
- ).map(value => ({ ...value.request.binding.target }));
2732
+ );
2733
+ }
2734
+ function protectedLiveRebindOldTargets(snapshot: TelegramWorkspaceRestoreSnapshot | undefined): TelegramTarget[] {
2735
+ return protectedLiveRebindOldTargetIntents(snapshot).map(value => ({ ...value.request.binding.target }));
2730
2736
  }
2731
2737
  function pendingWorkspaceRelocationRequests(
2732
2738
  snapshot: TelegramWorkspaceRestoreSnapshot | undefined,
@@ -2821,10 +2827,11 @@ function assertWorkspaceRestoreBindingProtection(
2821
2827
  throw new Error("Protected Workspace Restore owner target changed.");
2822
2828
  }
2823
2829
  }
2824
- for (const old of protectedLiveRebindOldTargets(file.workspaceRestore)) {
2830
+ for (const { request } of protectedLiveRebindOldTargetIntents(file.workspaceRestore)) {
2831
+ const old = request.binding.target;
2825
2832
  if (file.workspaceBindings?.some(value => targetMatches(value.target, old)) ||
2826
2833
  file.threads?.some(value => targetMatches(value.target, old) && ["active", "starting", "pending", "probe-required"].includes(value.status)) ||
2827
- file.pendingProvisions?.some(value => !value.target || targetMatches(value.target, old)) ||
2834
+ file.pendingProvisions?.some(value => conflictsWithWorkspaceRestoreProvision(request, value)) ||
2828
2835
  file.reservations?.some(value => targetMatches(value.target, old)))
2829
2836
  throw new Error("Unknown live cleanup target remains protected.");
2830
2837
  }
@@ -3840,7 +3847,7 @@ export function createTelegramTopicTargetStore(
3840
3847
  };
3841
3848
  const assertRestoreRecoveryProtection = (
3842
3849
  path: string,
3843
- operations: readonly TelegramWorkspaceRestoreIntent[],
3850
+ operations: readonly Pick<TelegramWorkspaceRestoreIntent, "request">[],
3844
3851
  provisions: readonly TelegramThreadPendingProvision[],
3845
3852
  requireKnownTargets = false,
3846
3853
  recoveryEvidence?: TelegramProvisionRecoveryFile,
@@ -5061,7 +5068,7 @@ export function createTelegramTopicTargetStore(
5061
5068
  assertWorkspaceRestoreBindingProtection(file);
5062
5069
  assertRestoreRecoveryProtection(
5063
5070
  path,
5064
- file.workspaceRestore!.operations,
5071
+ provisioningProtectionIntents(file.workspaceRestore!),
5065
5072
  file.pendingProvisions ?? [],
5066
5073
  );
5067
5074
  observe({
@@ -5117,7 +5124,7 @@ export function createTelegramTopicTargetStore(
5117
5124
  assertWorkspaceRestoreBindingProtection(file);
5118
5125
  assertRestoreRecoveryProtection(
5119
5126
  path,
5120
- file.workspaceRestore!.operations,
5127
+ provisioningProtectionIntents(file.workspaceRestore!),
5121
5128
  file.pendingProvisions ?? [],
5122
5129
  );
5123
5130
  const canonical = file.workspaceBindings ?? [],
@@ -5175,9 +5182,9 @@ export function createTelegramTopicTargetStore(
5175
5182
  (row) => currentRow(row) && targetMatches(row.target, old),
5176
5183
  ) ||
5177
5184
  reserved.some((value) => targetMatches(value.target, old)) ||
5178
- provisions.some(
5179
- (value) => !value.target || targetMatches(value.target, old),
5180
- ) ||
5185
+ // Use the same binding/slot/owner/target conflicts as rebind admission. An unrelated unlocated
5186
+ // create must not globally veto cleanup of this exact former binding (nor its issued marker).
5187
+ provisions.some((value) => conflictsWithWorkspaceRestoreProvision(request, value)) ||
5181
5188
  cleanups.some((value) => targetMatches(value.target, old));
5182
5189
  if (
5183
5190
  protectedTarget(
@@ -5390,8 +5397,11 @@ export function createTelegramTopicTargetStore(
5390
5397
  file.reservations?.some((value) =>
5391
5398
  targetMatches(value.target, target),
5392
5399
  ) ||
5400
+ // This exact created frame already proves a separately created routing tab (API ACK or authenticated
5401
+ // implicit creation). An unlocated Workspace create is not its owner; keep that intent unresolved,
5402
+ // but veto a concrete target collision. Generic unknown-target/Restore cleanup keeps its own guards.
5393
5403
  file.pendingProvisions?.some(
5394
- (value) => !value.target || targetMatches(value.target, target),
5404
+ (value) => !!value.target && targetMatches(value.target, target),
5395
5405
  ) ||
5396
5406
  file.pendingCleanups?.some((value) =>
5397
5407
  targetMatches(value.target, target),
@@ -5557,7 +5567,7 @@ export function createTelegramTopicTargetStore(
5557
5567
  parseTopicTargetFile(before.file).pendingProvisions ?? [];
5558
5568
  assertRestoreRecoveryProtection(
5559
5569
  path,
5560
- before.state.operations,
5570
+ provisioningProtectionIntents(before.state),
5561
5571
  provisions,
5562
5572
  );
5563
5573
  const after = { ...before.file, workspaceRestore: next };
@@ -5573,7 +5583,7 @@ export function createTelegramTopicTargetStore(
5573
5583
  return false;
5574
5584
  assertRestoreRecoveryProtection(
5575
5585
  path,
5576
- [...before.state.operations, ...next.operations],
5586
+ [...provisioningProtectionIntents(before.state), ...provisioningProtectionIntents(next)],
5577
5587
  provisions,
5578
5588
  );
5579
5589
  committedBefore = before;
@@ -5598,7 +5608,7 @@ export function createTelegramTopicTargetStore(
5598
5608
  );
5599
5609
  assertRestoreRecoveryProtection(
5600
5610
  path,
5601
- [...before.state.operations, ...next.operations],
5611
+ [...provisioningProtectionIntents(before.state), ...provisioningProtectionIntents(next)],
5602
5612
  provisions,
5603
5613
  );
5604
5614
  if (!currentScope() || !isCurrent()) return;
@@ -6775,7 +6785,7 @@ export function createTelegramTopicTargetStore(
6775
6785
  conflictsWithWorkspaceRestoreProvision(request, provision),
6776
6786
  ) ||
6777
6787
  conflictsWithTemporaryThread(workspaceRestore, provision.target) ||
6778
- protectedLiveRebindOldTargets(workspaceRestore).some(old => !provision.target || targetMatches(old, provision.target))
6788
+ protectedLiveRebindOldTargetIntents(workspaceRestore).some(({ request }) => conflictsWithWorkspaceRestoreProvision(request, provision))
6779
6789
  ) {
6780
6790
  throw new Error("Protected Workspace Restore provisioning conflict.");
6781
6791
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.57.0",
3
+ "version": "0.57.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-kit",
3
- "version": "0.35.0",
3
+ "version": "0.35.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -47,7 +47,7 @@
47
47
  "@llblab/pi-codex-usage": "0.13.1",
48
48
  "@llblab/pi-grow-loop": "0.9.0",
49
49
  "@llblab/pi-state-flow": "0.26.4",
50
- "@llblab/pi-telegram": "0.57.0",
50
+ "@llblab/pi-telegram": "0.57.1",
51
51
  "@llblab/skills": "1.15.0"
52
52
  },
53
53
  "bundledDependencies": [